Shiklo Tools
PDF40
Image20
Calculators & Converters25
Text & Writing13
Developer26
Video14
Audio14
Document14
Data & Spreadsheet12
Web & SEO9
OCR & Scanning5
Archive & File12
Security & Privacy23
Health & Medical29
QR & Barcode5

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']));
}
Output
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));
}
Output
<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());
}
Output
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

  1. Write fn smallest<T: PartialOrd + Copy>(items: &[T]) -> T and test it with numbers and characters.
  2. Make a generic Wrapper<T> struct with a value: T field and a get(&self) -> &T method.
  3. Call largest with a vector of String and read the compiler's message about the Copy bound.