Skip to content

Generics Reference

Profile: :core Spec: SPEC-026


Type parameters in square brackets between function name and argument list:

func identity[T](x: T) -> T do
return x
end

Multiple parameters:

func zip[A, B](a: A, b: B) -> A do
return a
end
func min[T: Ord](a: T, b: T) -> T do
if a <= b do return a end
return b
end

Multiple bounds with +:

func clamp_and_add[T: Ord + Numeric](value: T, lo: T, hi: T, offset: T) -> T do
if value < lo do return lo + offset end
if value > hi do return hi + offset end
return value + offset
end

Type argument is always explicit:

let result = min[i64](3, 7)
let x = identity[i64](identity[i64](42))

trait Eq {
func eq(self, other: Self) -> bool
func ne(self, other: Self) -> bool
}

Enables == and !=. Satisfied intrinsically by all integer types, floats, and bool.

trait Ord: Eq {
func lt(self, other: Self) -> bool
func le(self, other: Self) -> bool
func gt(self, other: Self) -> bool
func ge(self, other: Self) -> bool
}

Enables <, <=, >, >=. Implies Eq. Satisfied by all integer and float types.

trait Numeric: Ord + Eq {
func add(self, other: Self) -> Self
func sub(self, other: Self) -> Self
func mul(self, other: Self) -> Self
func div(self, other: Self) -> Self
}

Enables +, -, *, /. Implies Ord and Eq.

TypeEqOrdNumeric
i8, i16, i32, i64
u8, u16, u32, u64
f32, f64
bool

No impl blocks required for primitives. The TraitOracle resolves these intrinsically.


The TraitOracle uses fixpoint iteration: if T satisfies Numeric, it automatically satisfies Ord and Eq without any additional annotations.

Numeric → Ord → Eq

Constraint violation messages include supertrait context:

error: type `bool` does not satisfy trait bound `Ord` required for `min[T=bool]`
note: `Ord` requires `Eq` (supertrait: `trait Ord: Eq`)

Returns the smaller of a and b.

func min[T](a: T, b: T) -> T do
if a <= b do return a end
return b
end

Returns the larger of a and b.

func max[T](a: T, b: T) -> T do
if a >= b do return a end
return b
end

Constrains value to [lo, hi].

func clamp[T](value: T, lo: T, hi: T) -> T do
if value < lo do return lo end
if value > hi do return hi end
return value
end
func main() do
print_int(clamp[i64](2, 5, 10)) // 5
print_int(clamp[i64](7, 5, 10)) // 7
print_int(clamp[i64](15, 5, 10)) // 10
end

Some generic functions are backed by compiler intrinsics with type-constant injection. The lowerer resolves the concrete type from ctx.type_substitution and appends type-specific constants before LLVM emission:

func truncate[T](value: i64) -> T do
@intrinsic("truncate", value) // lowerer injects: bit_width
end
func toInt[T](value: i64) !T do
@intrinsic("int_cast_checked", value) // lowerer injects: min, max
end

See std.core.conv for full documentation.


Monomorphization in lower.zig:

  1. Call site with [T=i64] collected into a TypeSubstitution
  2. Function body lowered with T substituted throughout
  3. Mangled name emitted (e.g., min_i64)
  4. Result cached in mono_cache — each unique (name, type_args) pair lowered once

  • No type inference[T] must be explicit at every call site
  • Single-param substitutionctx.type_substitution uses first mapped type for intrinsic constant injection (sufficient for single-T functions)
  • Pre-existing leakmono_cache mangled name strings not freed on teardown (tracked, non-blocking)