13. Generics
13.1 Declarations and constraints#
func first[T](values: const T[]) -> T?:
if values.length == 0: return none
return values[0]
struct Pair[A, B]:
pub let first: A
pub let second: B
func maximum[T: Comparable](left: T, right: T) -> T:
if left.compare(right) >= 0: return left
return right
Type parameters are declared in square brackets and usually inferred; explicit arguments use the same brackets, decode[Header](bytes). A constraint is one or more interfaces joined by &. A generic body type-checks from its declaration and written constraints alone; it never accepts syntax that happens to work for one instantiation. Generic code is monomorphised: each instantiation is compiled separately, and the compiler reports every instantiation's origin and size and rejects an infinite chain.
A type argument to a Base generic may be any type a Base module can spell: scalars, pointers, spans, structs, enums, unions, interface views, function types. It may not be a runtime-dependent type of full Luce. Inside Base this is automatic; at the boundary, §18.11 states it.
There are no value parameters (array length is the one built-in exception), no variadic generics, no specialisation, no compile-time code execution, and no associated types.
Why monomorphisation and constraints, not comptime. Zig checks a generic body only at instantiation, and its users report that constraints end up buried in the body and errors appear at the wrong site. Constrained generics checked at declaration give the C programmer what templates and void* were standing in for, with errors at the declaration. The cost is the closed list of things generics cannot express, which is short and stated.