The motivation for generics (Rust)
Write the logic once with a type parameter so it works for many types without duplication.
When you need the same logic for different types, copying the function and changing the type leads to duplication: two (or more) nearly identical functions that differ only in the types they use.
Any bug fix or improvement must be applied in every copy, and the code is harder to maintain. This is the motivation for generics: write the logic once with a type parameter so it works for many types without duplication.
fn largest_i32(list: &[i32]) -> Option<i32> {
let mut largest = *list.first()?;
for &n in list.iter().skip(1) {
if n > largest {
largest = n;
}
}
Some(largest)
}
fn largest_char(list: &[char]) -> Option<char> {
let mut largest = *list.first()?;
for &c in list.iter().skip(1) {
if c > largest {
largest = c;
}
}
Some(largest)
}
fn main() {
let numbers = [34, 50, 25, 100, 65];
let chars = ['y', 'm', 'a', 'q'];
println!("{:?}", largest_i32(&numbers));
println!("{:?}", largest_char(&chars));
}The logic of “find the largest element” is the same; only the type changes. With generics we can write one function that works for both.
Writing the same function for different types
You end up writing the same function twice (e.g. largest_i32 and largest_char) with identical logic, only changing the types. That’s tedious and error‑prone: a bug fix must be applied in every copy.
The way out is to use a type parameter (e.g. largest<T>) so one function works for many types.
fn largest<T: PartialOrd + Copy>(list: &[T]) -> Option<T> {
if list.is_empty() {
return None;
}
let mut largest = list[0];
for &item in list.iter().skip(1) {
if item > largest {
largest = item;
}
}
Some(largest)
}Generics let you accept any type T, but often you need T to support specific operations (e.g. comparison with >).
Trait bounds restrict the generic: you say “T must implement this trait.”
For example, largest needs to compare and copy elements, so we require T: PartialOrd + Copy; then one generic function works for i32, char, and any other type that implements those traits.
But what’s happening behind the scenes?
Monomorphization (Yeah, only just found out that this word exists myself) is how Rust compiles generics without runtime cost.
The compiler looks at every place you call a generic function with a concrete type and generates a dedicated copy of that function for that type.
So one generic largest<T> in source becomes, in the binary, separate code for largest with i32, with char, and so on.
There is no runtime type checking for each call uses the specialised version. You keep one abstract implementation in source and get the performance of hand-written copies per type.
Pretty sweet!
PHP to Rust
This content has been adapted from my PHP to Rust course — it’s still a few weeks off yet.
I’m planning to sign it off with a fully functioning API and front end, so it’s not just theory — it’s something real you can build and run. My hope is that it helps a few people along the way.
It’s already changing the way I think about code, especially around Rust’s borrowing and ownership rules.
P.S – I did a logo.
P.P.S – Let me know in the comments if you’re interested in the course — if there’s enough interest, I’ll double down and get it over the line faster.


