14. Traits (definition, implementation, default methods, trait objects)
Traits are contracts that say "this type behaves like X." They play a role similar to Java/Kotlin interfaces or Haskell typeclasses — except Rust traits can also bundle default methods, associated types, and consts, which is what makes the chainable iterator API possible.
What you'll learn
- 1Define a trait and implement it for multiple types
- 2Use default methods to share behavior
- 3Constrain generic functions with trait bounds
- 4Tell `dyn Trait` (dynamic dispatch) from `impl Trait` (static)
- 5Derive standard traits like Debug, Clone, Default and write your own where needed
Overview
If you know Java's `interface`, traits are its souped-up cousin — they not only declare contracts but also bundle default implementations, associated types, and associated constants. That's what makes the standard library's chainable methods (Iterator etc.) possible.
Core Concepts
1) Defining and implementing a trait
trait Greet {
fn hello(&self) -> String;
// default method — implementors can keep this version
fn shout(&self) -> String {
format!("{}!!!", self.hello())
}
}
struct Korean;
impl Greet for Korean {
fn hello(&self) -> String { "hello".into() }
}2) Trait bounds — constraining generics
fn print_hello<T: Greet>(t: &T) {
println!("{}", t.hello());
}
// equivalent with where clause
fn print_hello2<T>(t: &T) where T: Greet { /* ... */ }3) impl Trait — anonymous return type
`impl Trait` in a function's return slot says "some type implementing this trait," the concrete type inferred by the compiler.
4) dyn Trait — dynamic dispatch
| Aspect | impl Trait / generic | dyn Trait |
|---|---|---|
| Dispatch | Static (compile time) | Dynamic (runtime vtable) |
| Speed | Faster (inlinable) | Small call overhead |
| Heterogeneous collection | ✗ (uniform type) | ✓ (Vec<Box<dyn Trait>>) |
| Binary size | Can grow via monomorphization | Smaller |
5) Common standard traits
- **Debug** — `{:?}` formatting, auto-derivable with `#[derive(Debug)]`
- **Display** — `{}` formatting, implement by hand
- **Clone / Copy** — duplicate / bitwise copy
- **Default** — `Type::default()`
- **PartialEq / Eq** — `==`
- **Iterator** — for-loop and chainable methods
Hands-on Examples
Define a trait, implement it for two types:
trait Area {
fn area(&self) -> f64;
fn report(&self) {
println!("area = {:.2}", self.area());
}
}
struct Circle { r: f64 }
struct Square { s: f64 }
impl Area for Circle {
fn area(&self) -> f64 { std::f64::consts::PI * self.r * self.r }
}
impl Area for Square {
fn area(&self) -> f64 { self.s * self.s }
}
fn main() {
Circle { r: 2.0 }.report(); // area = 12.57
Square { s: 3.0 }.report(); // area = 9.00
}dyn Trait for heterogeneous collections:
fn main() {
let shapes: Vec<Box<dyn Area>> = vec![
Box::new(Circle { r: 1.0 }),
Box::new(Square { s: 2.0 }),
];
for s in &shapes { s.report(); }
}Derive standard traits:
#[derive(Debug, Clone, PartialEq, Default)]
struct Point { x: f64, y: f64 }
fn main() {
let p1 = Point::default(); // (0.0, 0.0)
let p2 = p1.clone();
println!("{:?} == {:?} ? {}", p1, p2, p1 == p2);
}Common Mistakes
Q. impl Trait vs. dyn Trait — which?
A. **Need to mix concrete types in one collection? Use dyn.** Otherwise prefer static dispatch (impl Trait / generic) — it inlines and runs faster. Library return types often use impl Trait to hide implementation details cleanly.
Q. What's the orphan rule?
A. **Either the type or the trait must live in your own crate** to impl a trait. "Implement Display for Vec<i32> directly" is forbidden — wrap it in a newtype.
Q. Can I derive Display?
A. Not in std (Display formats are too free-form). Write `impl fmt::Display for X` by hand, or pull in a crate like derive_more.
Recap
- A trait declares behavior and can include default implementations
- Trait bounds restrict generics at compile time
- impl Trait = static dispatch, dyn Trait = dynamic
- Common traits (Debug / Clone / Default / PartialEq) are derivable
Try It Yourself
- Define `trait Summary { fn summarize(&self) -> String }` and implement for Article / Tweet
- Define a trait with a default method (Animal { fn name(); fn greet() { ... } }) and use it
- Put several animals in a `Vec<Box<dyn Animal>>` and call .greet() on each
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗