Generics
Generics decouple “the shape of a type” from “the concrete type”: types and functions alike take type parameters, and the instantiation is fixed only at the call site. Constraints are expressed with where clauses; the constraint surface is the capability surface.
Generic Types
struct Box<T>
{
public var value: T;
public func Get() -> T => value;
public func Replaced(nv: T) -> Box<T> => new Box<T>() { value: nv };
}
val bi = new Box<int>();
bi.value = 42;
print(bi.Get(), bi.Replaced(7).Get());
val bs = new Box<string>();
bs.value = "word";
print(bs.Get());42 7
wordBox<int> and Box<string> are different instantiations, each with its own member surface. Nested instantiations are legal: Box<list<int>> holds a list.
Generic Functions
func First<T>(xs: list<T>) -> T?
{
if xs.Count == 0
{
return null;
}
return xs[0];
}
val empty = new list<string>();
print(First(empty) == null);
val names = new list<string>();
names.Add("a");
print(First(names));True
awhere Constraints and Admitted Operators
Constraints are written before the return type (clause order: uses → fail → where → ->). A constraint declares “what T must be able to do”, and operators in the generic body are admitted according to the constraint:
import Math.*;
func Sum2<T>(a: T, b: T) where T: Numeric -> T
{
return a + b;
}
print(Sum2(2, 3), Sum2(1.5, 2.5));5 4Numeric is a protocol from std Math (the four arithmetic operations plus equality); built-in scalars satisfy it implicitly, and a + b is admitted by the constraint protocol’s operator signatures. Multiple constraints are allowed: where T: Ordered, Clone puts several constraints in one clause, or use several where clauses.
Constructor Signature Constraints
When a constraint protocol declares init(), the generic body gains the ability to write new T(), constructed via the concrete type’s constructor at instantiation:
import Collections.*;
protocol Makeable
{
init();
}
struct Token
{
public var stamp: string = "made";
}
func Make<T>() where T: Makeable -> T
{
return new T();
}
val t = Make<Token>();
print(t.stamp);madeWithout an init constraint, new T() is a compile-time error outright, because there is no way to know whether the T on the other side permits parameterless construction.
Unary Operator Constraints
Constraint protocols can also declare unary operators. operator -(self) -> Self; admits -x inside the generic body:
protocol Neg
{
operator -(self) -> Self;
}
func Negate<T>(x: T) where T: Neg -> T
{
return -x;
}
print(Negate(5));-5On the argument side, satisfaction is judged by built-in operator capability: uint / byte have no unary negation.
Notes
- Constraints go before the return type (uses → fail → where →
->), never to the right of->. - Multiple constraints can coexist:
where T: Ordered, Cloneputs several in one clause, or use several where clauses, one per type parameter. - Using an operator the constraint surface lacks reports MS3144; the error message names the protocol to add.
list<T>/map<K, V>are ready-made examples of generic containers; for the method surface see Collections and Text.
