RsRust · Lesson 6 of 8
Traits & Generics
Traits are Rust's version of interfaces. Generics let you write code that works across types. Together, they're how Rust achieves zero-cost abstraction.
Rust
// 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);
}Rust
// 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
}