Featureimpl Traitdyn Trait
DispatchStatic (compile-time)Dynamic (runtime)
PerformanceSlightly fasterOne indirection
Code sizeLarger (monomorphization)Smaller
Collection typesHomogeneous onlyHeterogeneous
Type known atCompile timeRuntime
SizeType-dependent2 words (fat pointer)

Syntax

&dyn Trait / &mut dyn Trait: Trait object (dynamic dispatch)

fn process(x: &dyn Display) { }

impl Trait: Static dispatch

fn process(x: impl Display) { }

Generics <T: Trait>: Also static dispatch (equivalent to impl Trait in parameters)

fn process<T: Display>(x: T) { }

Examples

dyn Trait

When: Heterogeneous collections or runtime type choice

// Store different types in same collection
struct App {
    plugins: Vec<Box<dyn Plugin>>,  // Can't know types at compile time
}
 
// Each plugin are different types that implements trait Plugin
app.plugins.push(Box::new(AudioPlugin));
app.plugins.push(Box::new(NetworkPlugin));
app.plugins.push(Box::new(DatabasePlugin));
 
for plugin in &app.plugins {
    plugin.execute();
}
// Store different types in same collection
let v: Vec<Box<dyn Display>> = vec![
    Box::new(42),  da      // i32
    Box::new("hello"),   // &str
    Box::new(3.14),      // f64
];
// Runtime decision
let x: Box<dyn Display> = if condition {
    Box::new(42)
} else {
    Box::new("text")
};

impl Trait

When: Each call site uses one specific type

// Just need "A thing that implements Display"
fn log(msg: impl Display) {
    println!("[LOG] {}", msg);
}
 
log(42);        // This call site: i32
log("error");   // This call site: &str
// Each call knows its exact type
// Cannot do this with impl:
let v: Vec<Box<impl Display>> = ...;  // ERROR: what type is impl?
// Vec can only hold ONE concrete type

Internal Implementation

dyn Trait (Dynamic Dispatch)

Fat pointer (2 words):

  • Pointer to data
  • Pointer to vtable (virtual method table)

Vtable contains function pointers for each trait method.

let x: &dyn Display = &42;
println!("{}", x);

NOTE

It is called dynamic because the compiler doesn’t know which function it’s calling until runtime, when it follows the vtable pointer.

Each type has its own implementation of the trait methods. The vtable stores pointers to these different implementations.

NOTE

Vtables are created at compile time and stored in the binary’s read-only data section. Each vtable correspond to a single trait impl definition.

Runtime:

  1. Dereference vtable pointer
  2. Look up fmt function pointer in vtable
  3. Call function via pointer
  • Slower (one indirection)
  • Single compiled function

impl Trait (Static Dispatch)

Compiler generates separate function for each type:

fn log(msg: impl Display) { println!("{}", msg); }
 
log(42);
log("hi");

Compiles to (conceptually):

fn log_i32(msg: i32) { println!("{}", msg); }
fn log_str(msg: &str) { println!("{}", msg); }
 
log_i32(42);
log_str("hi");
  • Direct function calls
  • Fast
  • Longer compile times with many types

Memory Layout

For brevity, & implies &mut here.

  • &dyn Display: 2 words (data ptr + vtable ptr) - fat pointer
  • &impl Display: 1 word (just the pointer to the value) - thin pointer
  • impl Display : size_of::<ConcreteDisplay>()