Generics
Generics allow you to write code that works with multiple types while preserving static type safety.
Generic Functions
Basic Type Parameters
Place type parameters in angle brackets after the function name:
fun identity<T>(x: T) -> T {
return x;
}
identity(42); // T is inferred as i32
identity("hello"); // T is inferred as string
identity(true); // T is inferred as boolType parameters are inferred from the arguments — you rarely need to specify them explicitly.
Multiple Type Parameters
fun pair<A, B>(a: A, b: B) -> (A, B) {
return (a, b);
}
val p = pair(1, "world"); // (i32, string)Type Constraints
You can constrain type parameters with a fixed set of allowed types:
fun add<T: i32 | f64>(a: T, b: T) -> T {
return a + b;
}
add(1, 2); // OK: i32
add(1.5, 2.5); // OK: f64
// add("a", "b"); // ERROR: string not in constraintParameter Packs
Parameter packs allow variable-arity generic functions. A pack parameter uses ... suffix and receives a tuple:
fun count<T...>(args: T...) -> u32 {
return args.size;
}
count(1, "two", 3.0, true); // returns 4Destructuring Packs
fun printEach<T...>(args: T...) {
if (args.size > 0) {
val (head, ...tail) = args;
print(head);
next ...tail; // recurse with remaining elements
}
}
printEach(1, "hello", true);Constraints on Packs
fun sum<T: i32 | f64...>(args: T...) -> T {
// Only works for numeric types
}Generic Types
Generic Object Types
Types can be parameterized too:
type Box<T> {
value: T;
}
val intBox = new Box<i32> { value: 42 };
val strBox = new Box<string> { value: "hello" };Generic Tagged Unions
type Option<T> = Some(value: T) | None;
fun unwrapOr<T>(opt: Option<T>, fallback: T) -> T {
switch (opt) {
case Some(v) { return v; }
case None { return fallback; }
}
}Generic Type Aliases
type Pair<T> = (T, T);
type Result<T, E> = Ok(value: T) | Err(error: E);Generic Wrapped Types
type Wrapper<T> wraps T;
val w = Wrapper<i32>(42);Generic Instance Mangling
Each generic instantiation gets a unique mangled name that encodes the module path and concrete type arguments. This ensures type safety across module boundaries:
// Module A
export fun foo<T>(x: T) -> T => x;
// Module B
import A;
val x = foo(42); // calls A::foo<i32>
val y = foo("hi"); // calls A::foo<string> — different instanceConst Generic Parameters
Type parameters can be constrained by compile-time constant values:
fun fixedArray<T, const N: i32>() -> array<T, N> {
// Returns a fixed-size array of type array<T, N>
}
val arr: array<i32, 5> = fixedArray<i32, 5>();What's Next?
Continue to References, Moves & Ownership to learn about NG's ownership model.
Try it:
example/15.generics.ng— Basic generics Try it:example/52.const_array_vector_span.ng— Const generic parameters