Course contentsShow
← Rust
Lesson 750 of 1,9106. Generics and Static DispatchFree lesson

Working with Generic Heterogeneous Lists

Manage collections of different types that all satisfy a core trait constraint.

In this lesson, you will learn how to organize collections that hold diverse types while ensuring they all adhere to a shared behavioral contract.

The Challenge of Heterogeneity

In Rust, a standard Vec<T> requires every element to be the exact same type. But what if you need a list that contains a Button, a Slider, and a Checkbox? If these structs all implement a trait like Renderable, you face a classic problem: how do you store them together when their memory sizes differ?

The Trait Object Approach

To create a heterogeneous list, we use Trait Objects. By storing a collection of Box<dyn Renderable>, we erase the specific type information at compile-time and rely on dynamic dispatch.

trait Renderable {
    fn render(&self);
}

struct Button;
struct Slider;

impl Renderable for Button { fn render(&self) { println!("Rendering button"); } }
impl Renderable for Slider { fn render(&self) { println!("Rendering slider"); } }

fn main() {
    // A heterogeneous list using trait objects
    let ui_elements: Vec<Box<dyn Renderable>> = vec![
        Box::new(Button),
        Box::new(Slider),
    ];

    for element in ui_elements {
        element.render();
    }
}

Why This Works

Because we define the collection as Box<dyn Renderable>, the compiler knows that every item in the list satisfies the Renderable contract. Even though the actual items have different memory layouts, Box puts them on the heap, allowing the list to contain pointers of uniform size. The "Heterogeneous" part refers to the fact that while the pointers are identical in size, the data they point to is varied.

Trade-offs

Using this pattern enables high flexibility, allowing you to append new types to your list without changing the list's definition, provided they implement the required trait. However, remember that because we are using dynamic dispatch, we trade a small amount of performance—and the ability to know the exact underlying type—for the sake of structural convenience.

Key Takeaway: Use Vec<Box<dyn Trait>> to manage collections of different types by enforcing a shared behavioral interface that allows for safe, uniform interaction.