Lesson 14
Generics
Write flexible, reusable code with generic types and trait bounds.
Generic Functions
Generics let you write code that works with many types without duplicating it. A generic function uses type parameters like <T> as placeholders. When you call the function, Rust figures out the concrete type automatically. This is called monomorphization -- there's zero runtime cost.
fn first<T>(list: &[T]) -> &T {
&list[0]
}
Generic Structs
Structs can be generic too. Point<T> can hold any type for its coordinates -- integers, floats, whatever you need. You can even use multiple type parameters like Point<T, U> when the fields might have different types.
Trait Bounds
Often your generic code needs the type to support certain operations. Trait bounds restrict which types can be used: <T: Display + Clone> means "T must implement both Display and Clone." For complex bounds, use a where clause to keep things readable.
fn print_all<T>(items: &[T])
where
T: std::fmt::Display,
{
for item in items {
println!("{}", item);
}
}
Try it
use std::fmt::Display;
// A generic struct with one type parameter
#[derive(Debug)]
struct Point<T> {
x: T,
y: T,
}
// Methods on a generic struct
impl<T: Display> Point<T> {
fn new(x: T, y: T) -> Self {
Point { x, y }
}
fn describe(&self) {
println!(" Point({}, {})", self.x, self.y);
}
}
// Additional methods only for f64 Points
impl Point<f64> {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
// A generic function with trait bounds
fn largest<T: PartialOrd + Display>(list: &[T]) -> &T {
let mut biggest = &list[0];
for item in &list[1..] {
if item > biggest {
biggest = item;
}
}
biggest
}
// Generic function with a where clause
fn print_if_positive<T>(value: T)
where
T: Display + PartialOrd + Default,
{
if value > T::default() {
println!(" {} is positive", value);
} else {
println!(" {} is not positive", value);
}
}
fn main() {
println!("--- Generic Point ---");
let int_point = Point::new(5, 10);
int_point.describe();
let float_point = Point::new(3.0_f64, 4.0_f64);
float_point.describe();
println!(" Distance from origin: {:.2}", float_point.distance_from_origin());
println!("\n--- Generic largest() ---");
let numbers = vec![34, 50, 25, 100, 65];
println!(" Largest number: {}", largest(&numbers));
let words = vec!["apple", "mango", "banana", "cherry"];
println!(" Largest word: {}", largest(&words));
println!("\n--- Trait Bounds with where ---");
print_if_positive(42);
print_if_positive(-3);
print_if_positive(0.0);
print_if_positive(3.14);
}Challenge
Make the Point struct work with mixed types by adding a second type parameter (e.g., Point<i32, f64>). Create a point with x as an integer and y as a float.
Previous: Traits
Next: Lifetimes