← Back to Rust series
🦀
Type system
Type system · Prerequisite: lesson 13

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.

Rusttraitinterfacedyngenericspolymorphism
Duration
~2 hours
Level
📊 Intermediate
Prerequisite
🎯 Lesson 13
OUTCOME
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

rust
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

rust
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

Aspectimpl Trait / genericdyn Trait
DispatchStatic (compile time)Dynamic (runtime vtable)
SpeedFaster (inlinable)Small call overhead
Heterogeneous collection✗ (uniform type)✓ (Vec<Box<dyn Trait>>)
Binary sizeCan grow via monomorphizationSmaller

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:

rust
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:

rust
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:

rust
#[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

  1. Define `trait Summary { fn summarize(&self) -> String }` and implement for Article / Tweet
  2. Define a trait with a default method (Animal { fn name(); fn greet() { ... } }) and use it
  3. Put several animals in a `Vec<Box<dyn Animal>>` and call .greet() on each
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗