In this lesson, you will learn how to bypass Rust's strict static typing to store heterogeneous types in a single collection using Box<dyn Trait>.
The Problem of Homogeneity
Rust’s compiler loves knowing the exact size of everything at compile time. If you try to create a Vec<T>, every element must have the same size and the same concrete type. But what if you are building a graphics engine where you need a single list to hold both Circle and Square structs? You cannot store them directly in a Vec because they have different memory layouts.
Type Erasure via dyn
This is where Type Erasure comes in. By wrapping an object in a Box<dyn Trait>, you are essentially telling the compiler: "I don't care about the specific struct anymore; I only care that this item implements a specific set of behaviors defined in the Trait."
When you use Box<dyn Shape>, the Box performs two tasks:
- Heap Allocation: It moves the concrete struct to the heap (addressing the "unknown size" problem).
- VTable Pointer: It stores the item as a fat pointer, which includes the address of the data and a pointer to a vtable (virtual method table). This vtable contains the function pointers for the trait methods, allowing the program to perform dynamic dispatch at runtime.
Example
trait Shape {
fn draw(&self);
}
struct Circle { radius: f64 }
struct Square { side: f64 }
impl Shape for Circle { fn draw(&self) { println!("Drawing circle"); } }
impl Shape for Square { fn draw(&self) { println!("Drawing square"); } }
fn main() {
// We erase the concrete types (Circle/Square) into the trait type
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 2.0 }),
Box::new(Square { side: 5.0 }),
];
for shape in shapes {
shape.draw(); // Dispatch happens via the vtable
}
}
The concrete types are "erased" from the perspective of the Vec. The compiler only knows that the items are things that satisfy Shape. While this introduces a small performance cost due to pointer indirection, it is the fundamental mechanism for achieving polymorphism in Rust.
Key Takeaway: Boxed trait objects decouple your collection from concrete types by moving data to the heap and using a vtable to resolve method calls at runtime.