← thecodex.expert · The Codex Family of Knowledge
Rust

Generics

Unlike languages that erase generic types at runtime, Rust generates a fully specialized version of your code for each concrete type — generics cost nothing at runtime.

Rust 1.95+ Zero-cost abstraction Last verified:
Where this bites you

The PartialOrd bound that makes largest generic over comparable types is also exactly where it can go quietly wrong. PartialOrd allows types — like floating-point numbers — where two values can be incomparable, not just less-than or greater-than. f64::NAN is incomparable to everything, including itself: every > comparison against it returns false. If a NaN ends up seeded as the starting "largest" value, nothing in the list — however large — will ever beat it, and the function returns NaN with no panic, no error, no warning.

Rustnan_bug.rs
let readings = [f64::NAN, 10.0, 47.0, 25.0];
println!("{}", largest(&readings));
// NaN -- the real maximum, 47.0, is never found
Canonical Definition

A generic type parameter, written in angle brackets like <T>, lets a function, struct, or enum work across many concrete types without duplicating code. Rust compiles generics via monomorphization: for every concrete type actually used, the compiler generates a fully specialized copy of the code at compile time — meaning generic code runs exactly as fast as if you had hand-written a version for each type, with zero runtime dispatch overhead.

A generic function

The trait bound T: PartialOrd restricts T to types that support comparison operators — without it, the compiler would not know whether > is even valid for an arbitrary T.

Rustgeneric_fn.rs
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

println!("{}", largest(&[10, 25, 3, 47]));       // 47, T inferred as i32
println!("{}", largest(&[1.2, 5.6, 3.1]));       // 5.6, T inferred as f64

Those calls work because every element involved is comparable. The bug above breaks that assumption: largest itself can't fix it, since PartialOrd doesn't require values to always be comparable — the fix belongs at the call site, filtering out the incomparable values before they ever reach the generic function:

Rustnan_fix.rs
let readings = [f64::NAN, 10.0, 47.0, 25.0];
let valid: Vec<f64> = readings.iter().copied().filter(|x| !x.is_nan()).collect();
println!("{}", largest(&valid));
// 47

Generic structs

A struct can be generic over one or more type parameters, letting the same struct definition hold any type without duplication.

Rustgeneric_struct.rs
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn new(x: T, y: T) -> Point<T> {
        Point { x, y }
    }
}

let int_point = Point::new(5, 10);        // Point<i32>
let float_point = Point::new(1.5, 2.5);   // Point<f64>, a DIFFERENT concrete type

Monomorphization: zero-cost, at the price of binary size

Because the compiler generates a separate copy of the generic code for every concrete type actually used, using the same generic function or struct with many different types can increase the compiled binary's size (sometimes called "code bloat") — the tradeoff for having no runtime dispatch or boxing overhead the way type-erased generics in other languages often require.

Sources

1
The Rust Project. The Rust Programming Language, "Generic Types, Traits, and Lifetimes", doc.rust-lang.org/book/ch10.