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