Generics & Trait Bounds
Generics & Trait Bounds
Section titled “Generics & Trait Bounds”“One function. Every type. Zero runtime cost.”
Janus generics are pure compile-time code generation. No boxing, no vtable, no runtime
type information. When you call min[i64](3, 7), the compiler instantiates a concrete
min_i64 function and emits LLVM for it directly.
1. Your First Generic Function
Section titled “1. Your First Generic Function”A type parameter appears in square brackets between the function name and the argument list:
func identity[T](x: T) -> T do return xend
func main() do let n = identity[i64](42) print_int(n) // 42endThe [T] declares the type parameter. At the call site, [i64] tells the compiler
which concrete type to use. The compiler generates identity_i64 and emits native code.
2. Trait Bounds
Section titled “2. Trait Bounds”Not all operations make sense for every type. min requires comparison — T must
implement the Ord trait.
func min[T: Ord](a: T, b: T) -> T do if a <= b do return a end return bend
func main() do let smallest = min[i64](3, 7) print_int(smallest) // 3endThe : Ord constrains T to only types that support <, <=, >, >=.
If you try min[bool](true, false), the compiler catches it:
error: type `bool` does not satisfy trait bound `Ord` required for `min[T=bool]` note: `Ord` requires `Eq` (supertrait: `trait Ord: Eq`)The Three Core Traits
Section titled “The Three Core Traits”| Trait | Operators | Implies |
|---|---|---|
Eq | ==, != | — |
Ord | <, <=, >, >= | Eq |
Numeric | +, -, *, / | Ord, Eq |
All integer and float primitives satisfy all three intrinsically. No impl blocks needed.
3. Supertrait Propagation
Section titled “3. Supertrait Propagation”Ord: Eq means every Ord type is also Eq automatically. This chain is the
supertrait hierarchy:
trait Ord: Eq { } // Ord implies Eqtrait Numeric: Ord + Eq { } // Numeric implies Ord and EqSo i64: Numeric — the compiler also knows i64: Ord and i64: Eq without any
additional annotations. bool satisfies only Eq (equality, not ordering).
4. clamp — Three Comparisons
Section titled “4. clamp — Three Comparisons”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 valueend
func main() do print_int(clamp[i64](2, 5, 10)) // 5 (below lo) print_int(clamp[i64](7, 5, 10)) // 7 (in range) print_int(clamp[i64](15, 5, 10)) // 10 (above hi)endclamp constrains a value to the range [lo, hi]. All three comparisons use Ord.
5. Multiple Type Parameters
Section titled “5. Multiple Type Parameters”func first[A, B](a: A, b: B) -> A do return aendEach parameter is named independently. Multiple bounds use +:
func bounded[T: Ord + Numeric](a: T, b: T) -> T do return a + bend6. Current Limitations
Section titled “6. Current Limitations”- No type inference — you must always spell out
[T]at the call site - Single
implcontext — user-defined types require explicitimplblocks (intrinsic for primitives) - No higher-kinded types —
T[U]type parameters not yet supported
These are :service and :sovereign profile targets.
What’s Next
Section titled “What’s Next”With generics, type-safe conversion becomes possible. The next step is
std.core.conv — the three conversion primitives (as, truncate,
toInt) that work across integer types without implicit coercions.