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.
// 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}Output
Section titled “Output”142larger = 13What the compiler sees
Section titled “What the compiler sees”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