Generics
Prerequisites: HashMap & HashSet
Generics let one function or type work with many types. You have already
used them: Vec<T>, Option<T> and Result<T, E> are all generic. The <T>
is a type parameter — a placeholder that is filled in with a concrete type
where the code is used.
Rust compiles generics by monomorphization: for each concrete type you use, the compiler generates a specialized copy of the code. Generic code therefore runs exactly as fast as code written by hand for each type; the cost is slightly longer compile times and larger binaries.
Syntax
fn first<T>(items: &[T]) -> Option<&T> { // generic function
items.first()
}
struct Pair<T> { // generic struct
left: T,
right: T,
}
impl<T> Pair<T> { // methods for every Pair<T>
fn swap(self) -> Pair<T> {
Pair { left: self.right, right: self.left }
}
}
enum Shape<T> { // generic enum
Circle(T),
Square(T),
}
A generic function can only do things that are valid for every possible
T. To compare, add or print values you must say which abilities T needs
with a trait bound, such as T: PartialOrd. Traits are the next lesson;
for now, PartialOrd means "can be compared with < and >" and Copy
means "can be copied like an integer".
Example
fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
let mut largest = items[0];
for &item in items {
if item > largest {
largest = item;
}
}
largest
}
#[derive(Debug)]
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn new(x: T, y: T) -> Self {
Point { x, y }
}
fn x(&self) -> &T {
&self.x
}
}
// Methods that exist only for Point<f64>.
impl Point<f64> {
fn distance_from_origin(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
}
// Two type parameters: the fields can have different types.
#[derive(Debug)]
struct Labeled<K, V> {
label: K,
value: V,
}
fn main() {
println!("{}", largest(&[34, 50, 25, 100, 65]));
println!("{}", largest(&[1.5, 0.2, 7.25]));
println!("{}", largest(&['y', 'm', 'a', 'q']));
let int_point = Point::new(5, 10);
let float_point = Point::new(3.0, 4.0);
println!("{:?} has x = {}", int_point, int_point.x());
println!("distance = {}", float_point.distance_from_origin());
let item = Labeled { label: "answer", value: 42 };
println!("{item:?} -> {}: {}", item.label, item.value);
}
Expected output
100
7.25
y
Point { x: 5, y: 10 } has x = 5
distance = 5
Labeled { label: "answer", value: 42 } -> answer: 42
int_point.distance_from_origin() would not compile: that method only exists
for Point<f64>.
Const generics
Type parameters can also be constant values, most often array lengths:
fn sum_all<const N: usize>(values: [i32; N]) -> i32 {
values.iter().sum()
}
fn main() {
println!("{}", sum_all([1, 2, 3])); // N = 3
println!("{}", sum_all([10, 20, 30, 40])); // N = 4
}
This prints 6 and then 100.
Common mistakes
- Forgetting the trait bound. Writing
fn largest<T>(items: &[T]) -> Tand thenitem > largestfails witherror[E0369]: binary operation `>` cannot be applied to type `T`— the compiler tells you to addT: PartialOrd. - Forgetting
<T>afterimpl.impl Point<T> { ... }makes Rust look for a concrete type calledT:error[E0412]: cannot find type `T` in this scope. Writeimpl<T> Point<T>. - Mixing types in a single parameter:
Point::new(5, 4.0)fails witherror[E0308]: mismatched typesbecause both fields share oneT. Use two parameters (Point<T, U>) if that is really what you want. - Adding generics "just in case". A function that is only ever called with
Stringis clearer taking&strorStringdirectly.
Practice
Write a generic struct Stack<T> wrapping a Vec<T> with methods new,
push, pop (returning Option<T>), and len. Push three string slices,
pop one, and print the popped value and the remaining length.
Show solution
struct Stack<T> {
items: Vec<T>,
}
impl<T> Stack<T> {
fn new() -> Self {
Stack { items: Vec::new() }
}
fn push(&mut self, item: T) {
self.items.push(item);
}
fn pop(&mut self) -> Option<T> {
self.items.pop()
}
fn len(&self) -> usize {
self.items.len()
}
}
fn main() {
let mut stack = Stack::new();
stack.push("a");
stack.push("b");
stack.push("c");
println!("popped {:?}", stack.pop());
println!("{} left", stack.len());
}
popped Some("c")
2 left