We use generics to create definitions for items like function signatures or structs, which we can then use with many different concrete data types. Generics allow you to write flexible, reusable code for multiple data types without duplicating logic.
fn main(){
let bigger = largest_i32(1,2);
let bigger_char = largets_char('a', 'b');
println!("{}", bigger);
println!("{}", bigger_char);
}
fn largest_i32(a: i32, b: i32) -> i32 {
if a > b {
a
} else {
b
}
}
fn largest_char(a: char, b: char) -> char {
if a > b {
a
} else {
b
}
}❌ Problem: Repeated logic for different types
fn largest<T: std::cmp::PartialOrd>(a: T, b: T) -> T {
if a > b {
a
} else {
b
}
}✅ Works for any type that implements PartialOrd (for comparison)
struct Point<T> {
x: T,
y: T,
}
fn main() {
let int_point = Point { x: 5, y: 10 };
let float_point = Point { x: 1.0, y: 4.0 };
}✅ Point<i32> and Point<f64> are two different concrete types generated from the same generic struct.
struct MixPoint<T, U> {
x: T,
y: U,
}
fn main() {
let p = MixPoint { x: 5, y: 3.2 };
}✅ You can use multiple generic parameters in a struct.
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}✅ Option and Result are built-in enums that use generics.
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}✅ You can implement methods on generic structs using the same generic type.
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}✅ Combine fields from different points into a new one with mixed types.
💡 Generics are monomorphized at compile-time:
- Rust generates a concrete version of your generic function/struct for each type used
- No runtime cost!