Generics & Bounds
Generics let one declaration work with multiple types. Generic parameters are written after the
declaration name. Type parameters use a name such as T; const generic parameters use
const Type Name.
Generic Functions
Section titled “Generic Functions”T id<T>(T value) { return value;}
Int a = id<Int>(42);_ b = id(42);Generic Types
Section titled “Generic Types”@region(a, b)struct Pair<A, B> { @life(a) A first;
@life(b) B second;}
Pair<Int, UInt8[]&> pair = Pair<Int, UInt8[]&>(first: 1, second: "one");Generic fields may be instantiated with lifetime-carrying types, so a generic nominal type declares and binds the public regions exposed by those fields.
Type arguments may include suffixes:
Pair<Int?, UInt8[]&> maybe_pair;Pair<Int&!, Int[3]^> mutable_ref_and_owner;Const Generic Parameters
Section titled “Const Generic Parameters”Const generic parameters are compile-time values:
struct StaticCount<N: const Int> { Int value;}
Int const_value<N: const Int>() { N}Const generic names are value-level names. They can be used in expressions, but not as type names. The inline form lowers to the same canonical predicate as a leading constraint:
@where(N: const Int)struct CanonicalCount<N> { Int value;}@where
Section titled “@where”@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);Declaration-Local Aliases
Section titled “Declaration-Local Aliases”@alias(Name = Type) creates a local type alias for the current declaration. The alias is visible
only in that declaration’s signature, constraints, members, and function body. It does not leak into
the module namespace:
@alias(Item = Int)@alias(Cmp = Fn<Bool, Item, Item>)Bool compare_with(Cmp cmp, Item left, Item right) { cmp(left, right)}Leading annotations are applied in source order. Generic parameters for the current declaration are
available before annotations are applied. Later annotations can use aliases introduced by earlier
@alias annotations, but earlier annotations cannot use aliases introduced later. One @alias(...)
can contain multiple comma-separated bindings. This is equivalent to splitting them into multiple
ordered @alias annotations:
@alias(Items = T[]&, Cmp = Fn<Bool, T, T>)@where(T: Hashable)Bool contains_match<T>(Items values, Cmp cmp, T left, T right) { cmp(left, right)}Equality Constraints
Section titled “Equality Constraints”Generic parameter equality:
@where(T == UInt8)Int use_byte<T>(T value);Use != to reject a concrete type shape:
@where(T != _^)struct PlainSlot<T> { T value;}_ inside a type shape is an anonymous placeholder. It does not introduce a generic name; it only
means that any type argument is accepted in that position. For example, _^ matches every owning
pointer type, and Vector<_> matches a Vector with any element type:
@where(T == Vector<_>)Int vector_only<T>(T value);
@where(T != _?)struct NonOptional<T> { T value;}Use : for trait bounds. Use == or != for concrete type-shape matching:
@where(T: Sequence<Element = UInt8>)Int count_bytes<T>(T value);
@where(T == _^)Int box_only<T>(T value);Negative trait bounds use ! before the trait name:
@where(T: !Mutable)struct ImmutableOnly<T> { T value;}Projected associated type equality uses T.[Trait].Assoc == Type:
@where(T.[Sequence].Element == UInt8)Int count_bytes<T>(T value);Associated Type Bindings
Section titled “Associated Type Bindings”Trait bounds can bind associated types:
@where(T: Sequence<Element = UInt8>)Int count<T>(T value);Mutable Generic Parameters
Section titled “Mutable Generic Parameters”Use the built-in Mutable bound when a type parameter must carry a type-level write capability:
@where(T: Mutable)struct Slot<T> { T value;}
Slot<Int*!> slot;Mutable currently matches T&! and T*!. Binding mutability such as Int value! is not a type
argument and therefore is not checked by generic bounds.
Errorable Returns
Section titled “Errorable Returns”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 success type:
T?@ParseErr parse_optional<T>(UInt8[]& text);Grammar Boundaries
Section titled “Grammar Boundaries”- Generic type parameters accept types. Const generic parameters accept compile-time values with an
explicit type, such as
N: const Int. @where(...)is written before the declaration, not after it.- Associated type projection constraints use the
T.[Trait].Name == Typeform.