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

Type Erasure and Heterogeneous Containers

Group different types that share a common trait into a single memory-contiguous collection.

In this lesson, you’ll learn how to store different types that share a common trait inside a single memory-contiguous collection like a Vec.

The Problem: Fixed-Size Requirements

In Rust, a Vec<T> requires all elements to be the exact same size and type. This is because the compiler needs to know how many bytes to jump in memory to find the "next" element. If you try to create a Vec<Dog | Cat>, where both implement an Animal trait, the compiler will stop you because a Dog and a Cat might have different memory layouts.

The Solution: Type Erasure

To solve this, we use Type Erasure. By wrapping our concrete types in a trait object (using the &dyn Trait or Box<dyn Trait> syntax), we "erase" the specific identity of the type.

Instead of storing the object directly, we store a fat pointer. A fat pointer contains two pieces of information:

  1. A pointer to the actual data (the instance).
  2. A pointer to the "vtable" (a lookup table containing the trait method implementations for that specific type).

Because all fat pointers are the same size, the Vec can now hold them in a contiguous block of memory.

Putting it into a Container

Here is how you would store different shapes in a single vector:

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

struct Circle(f64);
struct Square(f64);

impl Shape for Circle { fn area(&self) -> f64 { 3.14 * self.0 * self.0 } }
impl Shape for Square { fn area(&self) -> f64 { self.0 * self.0 } }

fn main() {
    // We use Box<dyn Shape> to give each item a known size on the heap
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle(2.0)),
        Box::new(Square(3.0)),
    ];

    for shape in shapes {
        println!("Area: {}", shape.area());
    }
}

By boxing the items, we turn them into trait objects. The Vec now contains a list of pointers, allowing you to iterate over a collection of different types as if they were a single, uniform interface. The specific "type" is hidden from the container, leaving only the contract defined by the trait.

Key Takeaway: You can create heterogeneous collections by using trait objects (Box<dyn Trait>), which erase the specific concrete type in favor of a uniform fat pointer, enabling storage in a contiguous memory structure.