Skip to content

Rust Generics

You’ve already been using generics without necessarily naming them — Vec<i32> and Option<T> both use them. This page covers writing your own generic functions and structs.

Say you want a function that returns the largest of a list of numbers, for both i32 and f64. Without generics, you’d write it twice:

fn largest_i32(numbers: &[i32]) -> i32 {
let mut largest = numbers[0];
for &n in numbers {
if n > largest {
largest = n;
}
}
largest
}
fn largest_f64(numbers: &[f64]) -> f64 {
let mut largest = numbers[0];
for &n in numbers {
if n > largest {
largest = n;
}
}
largest
}

Same logic, copy-pasted for each type. Generics let you write it once.

fn largest<T: PartialOrd + Copy>(numbers: &[T]) -> T {
let mut largest = numbers[0];
for &n in numbers {
if n > largest {
largest = n;
}
}
largest
}
fn main() {
let integers = [10, 25, 3, 47, 8];
let floats = [1.5, 2.8, 0.3];
println!("{}", largest(&integers));
println!("{}", largest(&floats));
}

Output:

47
2.8
▶ Try it Yourself

<T: PartialOrd + Copy> reads as “for any type T, as long as it can be compared (PartialOrd) and copied (Copy).” T is a placeholder — Rust generates a version of the function for whatever concrete type you actually call it with (i32, f64, and so on).

Structs can be generic too:

struct Pair<T> {
first: T,
second: T,
}
fn main() {
let numbers = Pair { first: 5, second: 10 };
let words = Pair { first: "hello", second: "world" };
println!("{} {}", numbers.first, numbers.second);
println!("{} {}", words.first, words.second);
}

Output:

5 10
hello world
▶ Try it Yourself

One Pair<T> definition works for numbers, text, or any other type — numbers is a Pair<i32> and words is a Pair<&str>, but you only wrote the struct once.

No. Rust generates a separate, specialized version of the function or struct for each concrete type you actually use (this is called monomorphization). By the time your program runs, there’s no generic code left — just ordinary, fully-typed functions, exactly as fast as if you’d hand-written each one.

You can add methods to a generic struct with an impl block, just like on the Structs page. The only new part is that the impl needs its own <T> too:

struct Pair<T> {
first: T,
second: T,
}
impl<T> Pair<T> {
fn new(first: T, second: T) -> Self {
Pair { first, second }
}
fn swap(self) -> Pair<T> {
Pair { first: self.second, second: self.first }
}
}
impl Pair<f64> {
fn average(&self) -> f64 {
(self.first + self.second) / 2.0
}
}
fn main() {
let words = Pair::new("left", "right").swap();
println!("{} {}", words.first, words.second);
let scores = Pair::new(7.0, 9.0);
println!("{}", scores.average());
}

Output:

right left
8
▶ Try it Yourself

There are two kinds of impl block here:

  • impl<T> Pair<T> means “these methods exist for a Pair of any type”. Read the first <T> as “for any type T”. Without it, Rust would think T is the name of a real type and complain that it can’t find it.
  • impl Pair<f64> has no <T>. Its methods exist only for Pair<f64>. That’s why average can use + and / 2.0. Try calling words.average() and the compiler will tell you there’s no such method for Pair<&str>.

A struct can also have more than one type parameter, like struct Pair<T, U> { first: T, second: U }, when the two fields may have different types. And when the trait bounds get long, you can move them into a where clause to keep the signature readable. That’s covered on the Advanced Traits page.

Write fn smallest<T: PartialOrd + Copy>(items: &[T]) -> T that returns the smallest item. Test it with [8, 3, 5] and with the characters ['r', 'u', 's', 't']. Hint: it’s the largest function from this page with one comparison flipped.

Show solution
fn smallest<T: PartialOrd + Copy>(items: &[T]) -> T {
let mut smallest = items[0];
for &item in items {
if item < smallest {
smallest = item;
}
}
smallest
}
fn main() {
println!("{}", smallest(&[8, 3, 5]));
println!("{}", smallest(&['r', 'u', 's', 't']));
}

Output:

3
r
▶ Try it Yourself

Add a method larger(&self) -> &T to Pair<T> that returns a reference to whichever field is bigger. Test it with a Pair of numbers and a Pair of words. Hint: comparing needs PartialOrd, so put the bound on the impl itself: impl<T: PartialOrd> Pair<T>.

Show solution
struct Pair<T> {
first: T,
second: T,
}
impl<T: PartialOrd> Pair<T> {
fn larger(&self) -> &T {
if self.first > self.second {
&self.first
} else {
&self.second
}
}
}
fn main() {
let numbers = Pair { first: 3, second: 9 };
let words = Pair { first: "pear", second: "apple" };
println!("{}", numbers.larger());
println!("{}", words.larger());
}

Output:

9
pear
▶ Try it Yourself
  • Generics let you write one function or struct that works with multiple types, instead of duplicating code.
  • <T> is a placeholder type; trait bounds (T: PartialOrd) restrict what T is allowed to be.
  • impl<T> Pair<T> adds methods for every T; impl Pair<f64> adds methods for one concrete type only.
  • Generic code is compiled into specialized versions per type — no runtime performance cost.

Quick check

1. What problem do generics solve?

2. In fn largest<T: PartialOrd + Copy>(...), what does PartialOrd + Copy do?

3. Does using generics cost anything at runtime compared to hand-written code?

4. What does a single struct Pair<T> definition let you do?

Score: 0 / 4