20.8 Generics: Compile-Time Polymorphism

While trait objects provide runtime polymorphism, Rust’s idiomatic approach for polymorphism, when possible, is through generics and traits, enabling compile-time polymorphism.

Generic code is written using type parameters constrained by traits (e.g., <T: Display>). The Rust compiler performs monomorphization: it generates specialized versions of the generic code for each concrete type used at the call sites.

Example: Generic Max Function

use std::cmp::PartialOrd;
use std::fmt::Display;

// Works for any type T that supports partial ordering and can be displayed.
fn print_larger<T: PartialOrd + Display>(a: T, b: T) {
    let larger = if a > b { a } else { b };
    println!("The larger value is: {}", larger);
}

fn main() {
    print_larger(5, 10);       // Works with i32
    print_larger(3.14, 2.71);  // Works with f64
    print_larger("apple", "banana"); // Works with &str
}

During compilation, specialized versions like print_larger_i32, print_larger_f64, and print_larger_str are effectively created. Method calls within these specialized functions are direct or potentially inlined, avoiding the runtime overhead of vtable lookups associated with trait objects. This leads to highly efficient code, equivalent to manually specialized code.