Bounds
An unconstrained type parameter promises nothing: the body may store, pass and return a T, but cannot call a method on it. A bound makes the promise. T: Scored admits only type arguments that provide the methods of the interface Scored, and in return the body may call those methods on any T.
Syntax
<T: Interface>
<T: First + Second + …>
<T: Interface, U>
A bound follows its parameter after a colon; several bounds are joined with +. A comma always starts the next type parameter, so <T: Scored, Display> declares a second parameter named Display rather than a second bound. Bounds may appear on the type parameters of functions, methods and generic types.
interface Scored {
func Score() -> int32;
}
interface Named {
func Name() -> char8[..];
}
func Best<T: Scored>(first: T, second: T) -> T {
if second.Score() > first.Score() {
return second;
}
return first;
}
func Announce<T: Scored + Named>(value: &T) {
PrintLine("{} has {}", value.Name(), value.Score());
}
Inside the body
A bounded T is exactly what its bounds say, and nothing more. Every requirement of every bound is callable on it; nothing else is:
error: no interface bound on type parameter 'T' provides method 'Score'
note: type parameter 'T' has no interface bounds
help: add a bound whose interface declares 'Score', as in 'T: SomeInterface'
The same holds for passing a T where an interface is expected: a T prints with {} only under a Display bound. Inside the body, Self in a bound's requirements stands for the type argument, so with Core::Equatable as the bound, left.Equals(right) takes another T — and a requirement returning Self returns a T.
Results stay concrete. Best called with two Player values returns a Player, with every field and method of Player available — it never becomes "some Scored".
Satisfying a bound
A type argument satisfies a bound when it has every method the interface requires. Implementing the interface with extend T : Scored { … } is the usual way to provide them, but a bound is structural: a type whose plain extend block declares the required methods satisfies it too.
struct Player {
name: char8[..];
points: int32;
}
extend Player : Scored {
func Score(self: &Player) -> int32 {
return self.points;
}
}
extend Player : Named {
func Name(self: &Player) -> char8[..] {
return self.name;
}
}
struct Team {
total: int32;
}
// No ': Scored', but Score is there, so Team satisfies the bound.
extend Team {
func Score(self: &Team) -> int32 {
return self.total;
}
}
Interface values are stricter: only a declared implementation converts to one. An interface that requires nothing, such as Core::Iterator, is satisfied by every type and only documents intent.
Primitives
A primitive type has no methods of its own, so it satisfies a bound only once an extend block gives it the methods:
extend int32 : Scored {
func Score(self: &int32) -> int32 {
return self;
}
}
After this Best<int32>(3, 9) is accepted, while Best(2.5, 1.5) is not — float64 has > but no Score. Note that bare literals make T an int, not an int32: Best(3, 9) fails naming 'int', and the explicit <int32> is what makes 3 and 9 int32 values.
Checked at each use
A bound is checked where a type argument meets it — at each call of a bounded function or method, and at each written instantiation of a bounded type — never somewhere inside the body. The type arguments are inferred first, then every bound is checked on its own, and the failing one is reported with the missing method:
error: type argument 'float64' does not satisfy interface bound 'Scored' on type parameter 'T'
note: interface 'Scored' requires method 'Score', which type 'float64' does not implement
note: type parameter 'T' of function 'Best' is bound by 'Scored'
help: implement the interface, as in 'extend float64: Scored { ... }'
A type that meets one bound of Scored + Named but not the other is refused just the same.
Bounds travel
Inside a generic body, a type parameter satisfies exactly the bounds it was declared with. A generic that calls a bounded generic must therefore promise at least what the callee demands:
func Margin<T: Scored>(first: T, second: T) -> int32 {
let winner = Best(first, second); // T is Scored here, as Best requires
return winner.Score() * 2 - first.Score() - second.Score();
}
Without the bound on Margin, the call to Best is refused:
error: type argument 'T' does not satisfy interface bound 'Scored' on type parameter 'T'
note: type parameter 'T' is not constrained by 'Scored'
note: type parameter 'T' of function 'Best' is bound by 'Scored'
help: add the bound to the enclosing declaration, as in 'T: Scored'
Static dispatch
Each instantiation calls the method of its own type argument directly, resolved when it is compiled: no interface value is built, and nothing is looked up at run time. The comparison with dynamic dispatch through an interface value is tabled in Interface values.
See also
- Interfaces — declaring the interfaces bounds name
- Generics — type parameters and inference
- Interface values — the dynamically dispatched alternative
- Learn: Generic bound, Multiple bounds