Skip to content

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.


A type parameter appears in square brackets between the function name and the argument list:

func identity[T](x: T) -> T do
return x
end
func main() do
let n = identity[i64](42)
print_int(n) // 42
end

The [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.


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 b
end
func main() do
let smallest = min[i64](3, 7)
print_int(smallest) // 3
end

The : 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`)
TraitOperatorsImplies
Eq==, !=
Ord<, <=, >, >=Eq
Numeric+, -, *, /Ord, Eq

All integer and float primitives satisfy all three intrinsically. No impl blocks needed.


Ord: Eq means every Ord type is also Eq automatically. This chain is the supertrait hierarchy:

trait Ord: Eq { } // Ord implies Eq
trait Numeric: Ord + Eq { } // Numeric implies Ord and Eq

So i64: Numeric — the compiler also knows i64: Ord and i64: Eq without any additional annotations. bool satisfies only Eq (equality, not ordering).


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 (below lo)
print_int(clamp[i64](7, 5, 10)) // 7 (in range)
print_int(clamp[i64](15, 5, 10)) // 10 (above hi)
end

clamp constrains a value to the range [lo, hi]. All three comparisons use Ord.


func first[A, B](a: A, b: B) -> A do
return a
end

Each parameter is named independently. Multiple bounds use +:

func bounded[T: Ord + Numeric](a: T, b: T) -> T do
return a + b
end

  • No type inference — you must always spell out [T] at the call site
  • Single impl context — user-defined types require explicit impl blocks (intrinsic for primitives)
  • No higher-kinded typesT[U] type parameters not yet supported

These are :service and :sovereign profile targets.


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.