Course contentsShow
← Rust
Lesson 569 of 1,9105. Type System and TraitsFree lesson

Dynamic Dispatch in Collections

Store heterogeneous types in containers using trait objects and boxing.

In this lesson, you will learn how to store different types that implement the same trait within a single collection using trait objects and boxing.

The Problem: Homogeneity

Normally, Rust collections like Vec<T> must contain elements of the exact same type. If you try to create a Vec<Dog> and try to push a Cat into it, the compiler will stop you. This is because Rust needs to know the exact size of every element in the vector at compile time to manage memory efficiently.

The Solution: Trait Objects

When you want to group different types together, you can use dynamic dispatch. By using a trait object (denoted by &dyn Trait or Box<dyn Trait>), you tell Rust, "I don't care exactly what this type is, as long as it implements this specific trait."

Because the compiler can no longer determine the size of these objects at compile time, we must wrap them in a Box. A Box places the object on the heap and leaves behind a pointer of a fixed size, allowing the collection to store them uniformly.

Consider this example where we store different shapes in a single list:

trait Drawable {
    fn draw(&self);
}

struct Circle;
struct Square;

impl Drawable for Circle { fn draw(&self) { println!("Drawing a circle"); } }
impl Drawable for Square { fn draw(&self) { println!("Drawing a square"); } }

fn main() {
    // We use Box<dyn Drawable> to store different types in one Vec
    let shapes: Vec<Box<dyn Drawable>> = vec![
        Box::new(Circle),
        Box::new(Square),
    ];

    for shape in shapes {
        shape.draw(); // Dynamic dispatch happens here
    }
}

How it Works

When you call shape.draw(), Rust performs a lookup in a "vtable" (virtual method table) associated with the trait object at runtime. This allows the program to jump to the correct implementation of draw depending on whether the current item is a Circle or a Square. This runtime lookup is what we call dynamic dispatch. While slightly slower than static dispatch (which uses generics), it provides the flexibility needed for heterogeneous collections.

Key Takeaway: Use Box<dyn Trait> to store multiple distinct types in a single collection when they all share a common trait.