Skip to content

Intermediate · Modules, crates, traits and generics · topic 30 of 50 · 25 min

Trait objects and dynamic dispatch

Explanation

A trait object (dyn Trait) lets you use values of different types through one trait, for example a Vec<Box<dyn Shape>> holding circles and squares together. The method to call is found at run time through a table of function pointers (a vtable). Use generics when the type is known at compile time and you want speed; use trait objects when you need a mixed collection or want to choose the type at run time.

The problem generics cannot solve

A Vec<T> holds values of one type. What if a drawing app needs a list with circles, squares and text boxes together? With generics, T must be one type. A trait object solves this: it is a value whose exact type is hidden, and all we know is that it implements a trait. You write it as dyn Trait, always behind some kind of pointer, such as Box<dyn Trait> or &dyn Trait.

rust
trait Shape {
    fn area(&self) -> f64;
    fn name(&self) -> String;
}

struct Circle { r: f64 }
struct Square { s: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.r * self.r }
    fn name(&self) -> String { "circle".into() }
}
impl Shape for Square {
    fn area(&self) -> f64 { self.s * self.s }
    fn name(&self) -> String { "square".into() }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { r: 1.0 }),
        Box::new(Square { s: 2.0 }),
    ];
    for s in &shapes {
        println!("{} {:.2}", s.name(), s.area());
    }
}
Different types in one Vec. Prints: circle 3.14, then square 4.00.

Why behind a pointer?

Circle and Square have different sizes. A Vec needs every item to be the same size. A Box (a pointer to heap memory, module 8) is always the same size, so a Vec of boxes works. dyn Shape on its own has no known size, which is why you never see a bare dyn Shape variable.

Dynamic dispatch and the vtable

Dispatch means choosing which function to call. With generics, the compiler knows the exact type and calls the right function directly: static dispatch. With trait objects, the type is only known at run time. Each Box<dyn Shape> is a "fat pointer" made of two pointers: one to the data, and one to a vtable, a small table of that type's method addresses. A call looks up the method in the table: dynamic dispatch.

output
Box<dyn Shape>
  data ptr ---> Circle { r: 1.0 }
  vtable ptr -> [ area: Circle::area, name: Circle::name, drop, size ]
One extra pointer and one extra lookup per call.

Generics or trait objects?

Generics (impl Trait / T: Trait)Trait objects (dyn Trait)
DispatchStatic, decided at compile timeDynamic, through a vtable at run time
SpeedFastest; can be inlinedSmall cost per call; usually fine
Mixed types in one collectionNoYes
Binary sizeGrows with each type usedOne copy of the code
Typical useAlgorithms, containers, hot loopsPlugins, UI widgets, handlers chosen at run time

Which traits can be trait objects?

Not every trait can be used as dyn Trait. The rules were called "object safety" and are now called "dyn compatibility". In simple words: methods must take self by reference (&self or &mut self), must not return Self, and must not have their own generic type parameters. Clone, for example, is not dyn compatible because clone returns Self. The compiler gives a clear error if you break these rules.

Returning one of several types

rust
fn make_shape(kind: &str) -> Box<dyn Shape> {
    if kind == "circle" {
        Box::new(Circle { r: 1.0 })
    } else {
        Box::new(Square { s: 1.0 })
    }
}
impl Shape could not do this, because the two branches return different types. Box<dyn Shape> can.

Common mistakes

  • Writing Vec<dyn Shape>: "the size cannot be known at compile time". Use Vec<Box<dyn Shape>>.
  • Forgetting the dyn keyword. In edition 2021 and later, a trait used as a type must be written dyn Trait.
  • Reaching for dyn everywhere because it looks like Java. Prefer generics unless you need mixed types or run-time choice.
  • Trying to make a trait object from a trait whose methods return Self or are generic.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Trait objects and dynamic dispatch · 1/6

Vec<Box<dyn Shape>>

A Vec of T holds one type. To mix circles and squares in one list, use trait objects.

Prefer a video by a teacher? Find free videos on “rust trait objects dyn box dynamic dispatch vs generics” on YouTube ↗

In real life: Payment providers chosen at run time

A checkout service supports several payment providers. Which one to use is read from configuration when the service starts, so the type is not known at compile time. The service stores a Box<dyn PaymentProvider>, and every request calls charge on it. Adding a new provider means a new struct with one impl block.

rust
trait PaymentProvider {
    fn charge(&self, paise: u64) -> Result<String, String>;
}

struct UpiGateway;
struct CardGateway;

impl PaymentProvider for UpiGateway {
    fn charge(&self, paise: u64) -> Result<String, String> {
        Ok(format!("UPI txn for {paise} paise"))
    }
}
impl PaymentProvider for CardGateway {
    fn charge(&self, paise: u64) -> Result<String, String> {
        if paise > 20_000_000 { Err("card limit exceeded".into()) } else { Ok("card txn".into()) }
    }
}

fn provider_from_config(name: &str) -> Box<dyn PaymentProvider> {
    match name {
        "upi" => Box::new(UpiGateway),
        _ => Box::new(CardGateway),
    }
}

fn main() {
    let p = provider_from_config("upi");
    println!("{:?}", p.charge(49_900));
}
Prints: Ok("UPI txn for 49900 paise")

Practice (0/3 done)

Task 1 · Question

Which type can hold a Circle and a Square in the same Vec?

Task 2 · Question

A function must return a Circle or a Square depending on input. Which return type works?

Task 3 · Practice

Create a trait Discount with fn apply(&self, amount: u32) -> u32, and three structs: Flat(u32), Percent(u32) and NoDiscount. Store them in a Vec<Box<dyn Discount>>, apply each to Rs 1,000, and print the results. Then write the same with a generic function and explain in one line when you would choose each.

Quick quiz

Question 1

1.What is dynamic dispatch?

Question 2

2.Why must dyn Trait be behind a pointer like Box?

Question 3

3.Which is usually faster in a tight loop?

Question 4

4.Why can Clone not be used as dyn Clone?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is dynamic dispatch?

Answer: Looking up the method through a vtable at run time

Trait objects use a vtable to find the method at run time.

Why must dyn Trait be behind a pointer like Box?

Answer: Its size is not known at compile time

Different implementing types have different sizes.

Which is usually faster in a tight loop?

Answer: Generics with a trait bound

Static dispatch allows inlining and avoids the vtable lookup.

Why can Clone not be used as dyn Clone?

Answer: clone returns Self, so it is not dyn compatible

Methods returning Self break dyn compatibility.

Which trait lets *b reach the value inside a Box?Related

Answer: Deref

Deref powers * and automatic dereferencing.

From Box<T>: values on the heap

What happens if you call a blocking function inside a tokio task?Related

Answer: It can stall other tasks on that worker thread

Blocking work belongs in spawn_blocking.

From Interview questions: traits, errors, pointers and concurrency

Are generics slower than hand-written code for each type?Related

Answer: No, monomorphization makes them as fast

Specialised copies are generated at compile time.

From Generics and trait bounds

What is a trait?Related

Answer: A set of methods types can promise to implement

Traits describe shared behaviour.

From Traits: shared behaviour

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next: Closures →

Next steps

  1. This is the last topic of “Modules, crates, traits and generics”. The module exam comes next.
  2. Module 7 exam: Modules, crates, traits and generics · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Closures, iterators and smart pointers. 50 topics left in the course before the final exam.

← Previous: Generics and trait bounds