thecodex.expert · The Codex Family of Knowledge
Rust

Traits and Trait Objects

Rust gives you a choice most languages don't: static dispatch (zero cost, resolved at compile time) or dynamic dispatch (runtime flexibility, a small performance cost) — and you decide per use, not per language.

Rust 1.96 dyn Trait Last verified:
Canonical Definition

A trait defines a set of methods a type can implement, similar in spirit to an interface. Using a trait as a generic bound (fn foo<T: Trait>) resolves at compile time via monomorphization — static dispatch, with zero runtime cost. A trait object, written dyn Trait, instead stores a pointer to the data plus a pointer to a table of its methods (a vtable), resolved at runtime — dynamic dispatch, which allows a single collection to hold several different concrete types, at a small runtime cost per call.

Defining and implementing a trait

A trait can include a default method implementation, which an implementing type can use as-is or override.

Rusttraits.rs
trait Shape {
    fn area(&self) -> f64;

    fn describe(&self) -> String {   // default method
        format!("This shape has area {:.2}", self.area())
    }
}

struct Circle { radius: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
    // describe() uses the default — not overridden
}

Static dispatch: generics with trait bounds

Using a trait as a generic bound resolves at compile time — the compiler generates a separate, fully specialized version for each concrete type used, exactly like the generics covered earlier.

Ruststatic_dispatch.rs
fn print_area<T: Shape>(shape: &T) {   // resolved at compile time
    println!("{}", shape.describe());
}

Dynamic dispatch: dyn Trait and trait objects

A collection needing to hold several different concrete types behind one interface uses dyn Trait — usually behind a Box, since a trait object's exact size isn't known at compile time.

Rusttrait_objects.rs
struct Square { side: f64 }
impl Shape for Square {
    fn area(&self) -> f64 { self.side * self.side }
}

let shapes: Vec<Box<dyn Shape>> = vec![
    Box::new(Circle { radius: 2.0 }),
    Box::new(Square { side: 3.0 }),
];

for shape in &shapes {
    println!("{}", shape.describe());   // resolved at RUNTIME via vtable
}

Sources

1
The Rust Project. The Rust Programming Language, "Traits: Defining Shared Behavior" and "Using Trait Objects", doc.rust-lang.org/book/ch10 & ch18.