Lesson 6 of 12
Traits & Generics
Learn how traits describe shared behavior and how let one piece of code work with many types, without any runtime cost.
// Define a trait
trait Describable {
fn describe(&self) -> String;
fn short_desc(&self) -> String {
// Default implementation
self.describe()[..50.min(self.describe().len())].to_string()
}
}
struct Cat {
name: String,
indoor: bool,
}
struct Car {
make: String,
year: u32,
}
impl Describable for Cat {
fn describe(&self) -> String {
let location = if self.indoor { "indoor" } else { "outdoor" };
format!("{} is an {} cat", self.name, location)
}
}
impl Describable for Car {
fn describe(&self) -> String {
format!("{} ({})", self.make, self.year)
}
}
// Generic function with trait bound
fn print_desc<T: Describable>(item: &T) {
println!("{}", item.describe());
}
// or with 'impl Trait' syntax (cleaner for simple cases)
fn print_short(item: &impl Describable) {
println!("{}", item.short_desc());
}
fn main() {
let cat = Cat { name: "Whiskers".to_string(), indoor: true };
let car = Car { make: "Tesla".to_string(), year: 2024 };
print_desc(&cat);
print_desc(&car);
}// Generic struct
#[derive(Debug)]
struct Pair<T> {
first: T,
second: T,
}
impl<T: PartialOrd + std::fmt::Display> Pair<T> {
fn new(first: T, second: T) -> Self {
Pair { first, second }
}
fn larger(&self) -> &T {
if self.first > self.second { &self.first } else { &self.second }
}
}
// Common standard library traits
use std::fmt;
struct Point { x: f64, y: f64 }
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let pair = Pair::new(5, 10);
println!("Larger: {}", pair.larger()); // Larger: 10
let p = Point { x: 1.0, y: 2.5 };
println!("{}", p); // (1, 2.5) — uses our Display impl
}Finished reading?Mark it done to track your progress.
Tip: use the ← and → keys to move between lessons.