Build the mental model
Without generics, a function that finds the largest value would need to be duplicated for `i32`, `f64`, `String`, and so on — a generic type parameter (`fn largest<T: PartialOrd>(list: &[T]) -> &T`) lets you write that logic exactly once for any of those types, with `T` acting as a placeholder resolved to a concrete type only at compile time. Java or TypeScript generics get erased at runtime (no generic info survives into compiled code, so checks happen at runtime), but Rust generics are entirely different: the compiler generates a specific version of a generic function for every concrete type it's actually used with (`i32`, `f64`, `String`) via a process called monomorphization — so generic code performs exactly as fast as hand-written duplicate code with zero runtime overhead, a true zero-cost abstraction. If a generic type parameter has no trait bound (`T: PartialOrd`, covered in depth in Lesson 14), the compiler knows nothing about `T`'s behavior and won't allow any operation on it, not even `>` comparison — a generic type should never be thought of as accepting "anything at all"; a trait bound is exactly what declares which operations are actually available.
Connect it to a real scenario
Write the content analyzer's "find the maximum value in a list" logic as a single generic function `fn find_max_stat<T: PartialOrd>(items: &[T]) -> Option<&T>`, reusable for both word-count (`u32`) lists and average-sentence-length (`f64`) lists. Because of monomorphization, distinct `find_max_stat::<u32>` and `find_max_stat::<f64>` versions get generated separately at compile time, so there is no runtime performance loss at all — it performs exactly as if you'd hand-written two duplicate functions. If you design a generic `Stats<T>` struct to hold min/max/average, you must specify the trait bound `T: PartialOrd + Copy` — without it, the compiler won't accept the min/max comparison operations at all.
Try the working example
fn find_max_stat<T: PartialOrd>(items: &[T]) -> Option<&T> {
let mut max = items.first()?;
for item in items {
if item > max {
max = item;
}
}
Some(max)
}
fn main() {
let word_counts = [420, 890, 615, 1200];
let avg_lengths = [4.2, 5.8, 3.9];
println!("{:?}", find_max_stat(&word_counts));
println!("{:?}", find_max_stat(&avg_lengths));
}You print `Some(1200)` and `Some(5.8)`, proving the one function works for both types.5-minute try-it
Write a generic function `fn average<T: Into<f64> + Copy>(items: &[T]) -> f64` that returns a list's average as an f64 — test it with both a `u32` list and an `i32` list.
One important caution
Writing a generic function with no trait bound at all, `fn largest<T>(list: &[T]) -> T`, then trying to use `>` comparison — the compiler reports "binary operation `>` cannot be applied"; a `T: PartialOrd` bound is required.
Assuming generic code shares one implementation at runtime the way type-erased Java generics do — Rust's monomorphization can unexpectedly grow binary size ("code bloat") since a separate version compiles per concrete type used.