Generics
Generic functions take one or more type parameters between <>. The
compiler infers T from the call-site arguments — no explicit annotation
needed. Functions with multiple type parameters (<A, B>) track each type
independently:
identity<T>, head<T>, and pair_first<A, B> work with int, str, bool, and arrays.
// Feature: Generic functions
// Syntax: `fn name<T>(x: T) -> T { ... }`
// When to use: logic that works with ANY type (identity, containers,
// swaps) preserving the concrete type through the return value.
// Identity — `T` is inferred at the call site.
fn identity<T>(x: T) -> T {
return x
}
print(identity(42)) // 42
print(identity("hi")) // hi
print(identity(true)) // true
// More than one arg of the same `T`.
fn first<T>(a: T, b: T) -> T {
return a
}
print(first(1, 2)) // 1
print(first("a", "b")) // a
// Generic with array.
fn head<T>(arr: [T]) -> T {
return arr[0]
}
print(head([10, 20, 30])) // 10
print(head(["x", "y"])) // x
// Multiple type params.
fn pair_first<A, B>(a: A, b: B) -> A {
return a
}
print(pair_first(7, "right-side")) // 7
// expected:
// 42
// hi
// true
// 1
// a
// 10
// x
// 7
Generic structs store any type without losing information about which one
it is. The concrete type is inferred from the construction literal. The impl
block of a generic struct carries along the type parameter:
Box<T>, Pair<A, B>, and Stack<T> with impl Stack including an associated method and an instance method.
// Feature: Generic structs
// Syntax: `struct Name<T> { field: T }`
// When to use: container that holds any type while preserving it.
struct Box<T> {
value: T,
}
// Concrete type comes from usage.
let bi = Box { value: 42 } // Box<int>
let bs = Box { value: "hello" } // Box<str>
print(bi.value) // 42
print(bs.value) // hello
// Multiple type parameters.
struct Pair<A, B> {
first: A,
second: B,
}
let p = Pair { first: 1, second: "one" }
print(p.first) // 1
print(p.second) // one
// `impl` for generic struct — the `<T>` is inferred from the type name.
// NOTE: `len` is dispatched at the runtime level for Array/Map, so we
// expose the size under a distinct name (`size`) to avoid the clash.
struct Stack<T> {
items: [T],
}
impl Stack {
fn make() -> Stack {
return Stack { items: [] }
}
fn size(self) -> int {
return self.items.len()
}
}
let s = Stack::make()
print(s.size()) // 0
Generic enums are the foundation of parametrized sum types — the same
mechanism the stdlib uses for Option<T> and Result<T, E>. match
destructures the payload and binds the variable to the concrete type:
Maybe<T> with variants Just(T) and Nothing; Either<L, R> with two parameters.
// Feature: Generic enums
// Syntax: `enum Name<T> { Variant(T), Other }`
// When to use: parameterized sum types — this is how the stdlib
// defines `Option<T>` and `Result<T, E>`.
enum Maybe<T> {
Just(T),
Nothing,
}
// Construction: the `T` type comes from the payload.
let just_int = Maybe.Just(42)
let just_str = Maybe.Just("hello")
let none: Maybe<int> = Maybe.Nothing
let r1 = match just_int {
Maybe::Just(v) => "got {v}",
Maybe::Nothing => "none",
}
print(r1) // got 42
let r2 = match just_str {
Maybe::Just(v) => "got '{v}'",
Maybe::Nothing => "none",
}
print(r2) // got 'hello'
let r3 = match none {
Maybe::Just(v) => "got {v}",
Maybe::Nothing => "none",
}
print(r3) // none
// Result<T, E> with two type parameters.
enum Either<L, R> {
Left(L),
Right(R),
}
let lf = Either.Left("error")
let rt = Either.Right(99)
print(match lf {
Either::Left(s) => "L:{s}",
Either::Right(n) => "R:{n}",
})
// L:error
print(match rt {
Either::Left(s) => "L:{s}",
Either::Right(n) => "R:{n}",
})
// R:99
Challenge
Write a generic function swap<A, B>(a: A, b: B) -> Pair<B, A> that returns
the values in reversed order. Use the Pair struct from the generic structs
example.
See also