Skip to content

08 · Generics

Generics let you write code that works for any type satisfying a set of constraints. Type parameters are written in angle brackets and can be bounded with :; unbounded parameters accept any type. All generics are monomorphized — the compiler generates a separate specialized copy for each concrete type used, so there is no boxing or dynamic dispatch.

generics.cssl
// Generic function — T must implement PartialOrd for comparison.
fn clamp<T: PartialOrd>(value: T, lo: T, hi: T) -> T {
if value < lo { lo }
else if value > hi { hi }
else { value }
}
// Generic struct — a pair of values of the same type.
struct Pair<T> {
first: T,
second: T,
}
impl<T: PartialOrd + Copy> Pair<T> {
fn new(first: T, second: T) -> Pair<T> {
Pair { first, second }
}
fn larger(&self) -> T {
if self.first >= self.second { self.first }
else { self.second }
}
}
fn main() !io {
// Works for f32.
println!("{}", clamp(2.5_f32, 0.0, 1.0)); // clamped to 1.0
// Works for i32.
println!("{}", clamp(42_i32, 0, 100)); // 42
let p = Pair::new(7_i32, 13_i32);
println!("larger = {}", p.larger()); // 13
}
1
42
larger = 13

T: PartialOrd + Copy is a compound bound — the type must implement both traits. At the call site clamp(2.5_f32, ...), the compiler substitutes T = f32, checks that f32: PartialOrd and generates a specialized clamp_f32. Adding a bound that the concrete type doesn’t satisfy is a compile error at the call site, not inside the generic definition.


Next → 09 · GPU Effects