Rust Generics
Imagine writing a function that finds the largest number in a list of i32, and then needing the same for f64, and then for char. Copying the function three times with different types would work, but it is tiring and easy to get wrong. Generics let you write the function once, with a placeholder for the type, and Rust fills in the real type wherever you use it.
You have been using generics all along. Vec<i32>, Option<String> and HashMap<&str, u32> are all generic types: the part in angle brackets says which type fills the placeholder.
A generic function
The placeholder type is written in angle brackets after the function's name. By tradition it is called T, for type:
fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
let mut best = items[0];
for &item in items {
if item > best {
best = item;
}
}
best
}
fn main() {
println!("{}", largest(&[34, 50, 25, 100, 65]));
println!("{}", largest(&[1.5, 0.2, 9.75]));
println!("{}", largest(&['r', 'u', 's', 't']));
}100 9.75 u
Trait bounds: what T must be able to do
Not every type can be compared with >. The part after the colon, PartialOrd + Copy, is a trait bound: it says "T can be any type, as long as it can be compared (PartialOrd) and copied (Copy)". If you try to use largest with a type that cannot be compared, the compiler stops you and names the missing trait. Bounds are how generics stay safe: the function can only use abilities it has asked for.
When there are several bounds, a where clause can make the signature easier to read:
use std::fmt::Display;
fn show_pair<A, B>(a: A, b: B) -> String
where
A: Display,
B: Display,
{
format!("<{}, {}>", a, b)
}
fn main() {
println!("{}", show_pair(7, "seven"));
println!("{}", show_pair('x', 2.5));
}<7, seven> <x, 2.5>
Generic structs
Structs can be generic too. This Pair can hold two values of any one type, and its method works for every type that can be compared:
struct Pair<T> {
first: T,
second: T,
}
impl<T: PartialOrd + Copy> Pair<T> {
fn bigger(&self) -> T {
if self.first > self.second { self.first } else { self.second }
}
}
fn main() {
let ages = Pair { first: 31, second: 27 };
let heights = Pair { first: 1.62, second: 1.75 };
println!("Older: {}", ages.bigger());
println!("Taller: {}", heights.bigger());
}Older: 31 Taller: 1.75
Generics cost nothing at run time
When you compile, Rust creates a separate, specialised copy of a generic function for each type you actually use, a process called monomorphisation. The result is as fast as if you had written each version by hand. You get the convenience of writing once and the speed of writing many times.
Practice exercises
- Write
fn smallest<T: PartialOrd + Copy>(items: &[T]) -> Tand test it with numbers and characters. - Make a generic
Wrapper<T>struct with avalue: Tfield and aget(&self) -> &Tmethod. - Call
largestwith a vector ofStringand read the compiler's message about theCopybound.