Generics allow us to define function signatures and data types with placeholder type instead of concrete types. This helps in writing more flexible and reusable code, without sacrificing the type safety and performance that Rust offers.
Basic Usage of Generics
Let’s start with a simple example to demonstrate the concept:
fn identity<T>(value: T) -> T {
value
}
let a = identity(5); // i32
let b = identity(5.5); // f64
let c = identity("Hello"); // &str
In the example above, the function identity is defined with a generic type T. When this function is called, Rust infers the type of T based on the provided argument.
Generic Data Types
Rust allows you to define generic structs and enums as well. Here’s an example using a struct:
struct Point<T> {
x: T,
y: T,
}
let int_point = Point { x: 5, y: 10 }; // Point<i32>
let float_point = Point { x: 1.2, y: 3.4 }; // Point<f64>
Multiple Generic Types
You can use multiple generic types by separating them with commas:
struct Pair<T, U> {
first: T,
second: U,
}
let pair = Pair { first: "Hello", second: 42 }; // Pair<&str, i32>
Generics in Enum Definitions
Enums can also have generics:
enum Result<T, E> {
Ok(T),
Err(E),
}
The Result type is a core part of Rust's error handling.
Generic Constraints & Traits
Sometimes, you want to restrict the types used with generics. Rust offers trait bounds to achieve this:
fn display<T: Display>(value: T) {
println!("{}", value);
}
Here, T: Display means that the generic type T must implement the Display trait.
Implementing Methods on Generic Structs
When implementing methods for a generic struct, you can also specify trait bounds:
impl<T: Display + PartialOrd> Pair<T, T> {
fn max(&self) -> &T {
if self.first >= self.second {
&self.first
} else {
&self.second
}
}
}
In the example above, T must implement both Display and PartialOrd traits.
The where Clause
While using generics with trait bounds, the syntax can sometimes get convoluted, especially with multiple bounds. Rust offers the where clause for a cleaner approach:
fn some_function<T, U>(t: T, u: U)
where
T: Display + Clone,
U: Clone + Debug,
{
// function body
}
By using the where clause, you can specify trait bounds in a more organized manner.
Lifetimes with Generics
In Rust, lifetimes specify how long references to data should remain valid. When working with generics, sometimes you need to specify lifetimes as well:
struct RefWrapper<'a, T> {
value: &'a T,
}
In the example above, 'a is a lifetime parameter, and T is a generic type parameter. This structure wraps a reference to a value of type T with a given lifetime 'a.
Associated Types with Generics
Traits can also define associated types. These allow for more flexible and concise trait definitions:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
In this example, Item is an associated type. Implementors of this trait will specify what Item should be, allowing for more varied and specific implementations.
Placeholder Types and the _ Operator
When you’re not interested in specifying or inferring a particular type for a generic, Rust allows you to use the _ placeholder:
let _numbers: Vec<_> = vec![1, 2, 3];
Here, Rust will infer the correct type for the vector’s items based on the provided values.
Advanced Trait Bounds with Generics
Beyond basic trait bounds, Rust allows for more advanced bounds that cater to specific needs:


