Traits in Rust
Traits define shared behavior that types can implement. They're like interfaces in other languages, but more powerful because they can provide default implementations.
You define a trait with the trait keyword, then implement it for your types with impl Trait for Type.
trait Greet {
fn greet(&self) -> String;
}
struct Person {
name: String,
}
impl Greet for Person {
fn greet(&self) -> String {
format!("Hello, my name is {}", self.name)
}
}
fn main() {
let alice = Person { name: "Alice".to_string() };
println!("{}", alice.greet());
}
Try it Yourself ->
Trait Bounds on Functions
Trait bounds let you specify what behavior a type must have. This makes your functions more flexible while maintaining type safety.
trait Summary {
fn summarize(&self) -> String;
}
struct Article {
title: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}: {}", self.title, &self.content[..50])
}
}
fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
fn main() {
let article = Article {
title: "Rust is Amazing".to_string(),
content: "Today we learned about traits and they are incredibly powerful...".to_string(),
};
notify(&article);
}
Try it Yourself ->
Multiple Trait Bounds
You can require multiple traits with + syntax or where clauses. This lets you be specific about what your function needs.
use std::fmt::{Display, Debug};
fn compare<T: Display + Debug>(a: &T, b: &T) {
println!("a: {}, b: {}", a, a);
println!("a debug: {:?}, b debug: {:?}", a, b);
}
fn main() {
let x = 42;
let y = 43;
compare(&x, &y);
}
Try it Yourself ->
Default Implementations
Traits can provide default method implementations. Types can use these defaults or override them with their own version.
trait Animal {
fn name(&self) -> &str;
fn sound(&self) -> &str;
fn describe(&self) -> String {
format!("{} says {}", self.name(), self.sound())
}
}
struct Dog { name: String }
struct Cat { name: String }
impl Animal for Dog {
fn name(&self) -> &str { &self.name }
fn sound(&self) -> &str { "Woof" }
}
impl Animal for Cat {
fn name(&self) -> &str { &self.name }
fn sound(&self) -> &str { "Meow" }
fn describe(&self) -> String {
format!("The cat {} is purring", self.name())
}
}
fn main() {
let dog = Dog { name: "Rex".to_string() };
let cat = Cat { name: "Whiskers".to_string() };
println!("{}", dog.describe());
println!("{}", cat.describe());
}
Try it Yourself ->
Trait Objects for Dynamic Dispatch
Trait objects (dyn Trait) allow you to use different types through a single interface. This enables polymorphism at runtime.
trait Drawable {
fn draw(&self);
}
struct Circle { radius: f64 }
struct Square { side: f64 }
impl Drawable for Circle {
fn draw(&self) {
println!("Drawing a circle with radius {}", self.radius);
}
}
impl Drawable for Square {
fn draw(&self) {
println!("Drawing a square with side {}", self.side);
}
}
fn main() {
let shapes: Vec<Box<dyn Drawable>> = vec![
Box::new(Circle { radius: 5.0 }),
Box::new(Square { side: 4.0 }),
];
for shape in &shapes {
shape.draw();
}
}
Try it Yourself ->
Common Traits
Rust has many built-in traits that you'll use frequently. Display for user-facing output, Debug for debugging, Clone for copying, and PartialEq for comparison.
Deriving these traits is often as simple as adding #[derive(TraitName)] above your struct. This saves you from writing boilerplate code.