Skip to content

Generics & Bounds

Generics let one declaration work with multiple types. Generic parameters are written after the declaration name.

T id<T>(T value) {
return value;
}
Int a = id<Int>(42);
_ b = id(42);
struct Pair<A, B> {
A first;
B second;
}
Pair<Int, UInt8[]> pair = Pair<Int, UInt8[]> {
first: 1,
second: "one",
}

Type arguments may include suffixes:

Pair<Int?, UInt8[]> maybe_pair;
Pair<Int!, Int[3]^> mutable_and_owner;

@where(...) is a leading annotation:

@where(T: Numeric)
T add<T>(T left, T right) {
return left + right;
}

Multiple constraints use commas:

@where(T: Hashable, U: Equatable)
Pair<T, U> make_pair<T, U>(T left, U right);

Intersection bounds use &:

@where(T: Hashable & Equatable)
UInt hash_key<T>(T value);

Generic parameter equality:

@where(T == UInt8)
Int use_byte<T>(T value);

Projected associated type equality uses T.[Trait].Assoc == Type:

@where(T.[Iterable].Item == UInt8)
Int count_bytes<T>(T value);

Trait bounds can bind associated types:

@where(T: Iterable<Item = UInt8>)
Int count<T>(T value);

Top-level mutability can be expressed through a Mutable bound:

@where(T: Mutable)
struct Slot<T> {
T value;
}
Slot<Int!> slot = Slot<Int!> { value: 1 }

Generic functions can return T@E in result position:

@where(T: Numeric)
T@ParseErr parse_number<T>(UInt8[] text);

If the success value is optional, put ? on the left side of @:

T?@ParseErr parse_optional<T>(UInt8[] text);
  • Generic parameters accept types, not non-type parameters.
  • @where(...) is written before the declaration, not after it.
  • Associated type projection constraints use the T.[Trait].Name == Type form.