Skip to content

Generics

Instead of writing largest_i32, largest_f64, and largest_char as separate functions, generics let you write one:

fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut biggest = &list[0];
for item in list {
if item > biggest {
biggest = item;
}
}
biggest
}

The <T: PartialOrd> part declares a type parameter T with a trait bound. The bound PartialOrd means “this function accepts any type that supports the > and < operators.” Without the bound, the compiler would refuse the comparison item > biggest because not every type is comparable.

Structs can also be parameterized. A Pair<T> holds two values of the same type T:

struct Pair<T> {
first: T,
second: T,
}

When you instantiate it, the compiler infers T from the values you provide:

let p = Pair { first: 5, second: 10 }; // T inferred as i32

To add methods to a generic struct, repeat the type parameter in the impl line:

impl<T: std::fmt::Display + PartialOrd> Pair<T> {
fn larger(&self) -> &T {
if self.first > self.second { &self.first } else { &self.second }
}
}

Here T must implement both Display (for printing) and PartialOrd (for comparison). You can constrain methods further than the struct itself requires — a Pair<T> can exist for any T, but larger is only available when T is comparable.

Inline bounds work well for one or two constraints. When a signature grows long, a where clause keeps it readable:

// Inline bounds
fn notify<T: Clone + std::fmt::Debug>(item: T) { ... }
// Equivalent where clause
fn notify<T>(item: T)
where
T: Clone + std::fmt::Debug,
{ ... }

Both forms are identical to the compiler; choose whichever is clearer.

You are not limited to one parameter. A function can accept two or more:

fn zip_display<A: std::fmt::Display, B: std::fmt::Display>(a: A, b: B) {
println!("{} + {}", a, b);
}

The playground demonstrates a generic largest function working on both integers and characters, plus a generic Pair<T> struct with a method constrained to comparable, displayable types.

fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut biggest = &list[0];
for item in list {
if item > biggest {
biggest = item;
}
}
biggest
}
struct Pair<T> {
first: T,
second: T,
}
impl<T: std::fmt::Display + PartialOrd> Pair<T> {
fn new(first: T, second: T) -> Self {
Self { first, second }
}
fn larger(&self) -> &T {
if self.first > self.second {
&self.first
} else {
&self.second
}
}
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
println!("largest number: {}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("largest char: {}", largest(&chars));
let p = Pair::new(5, 10);
println!("larger of pair: {}", p.larger());
}
What does the `T: PartialOrd` syntax in `fn largest<T: PartialOrd>(...)` mean?
What is monomorphization?
How would you declare a generic struct `Stack` that holds elements of type `T`?
What is the purpose of a `where` clause in a generic function?