Course contentsShow
← Rust
Lesson 1013 of 1,9109. Smart Pointers and Interior MutabilityFree lesson

Reference Counting and Trait Objects

Compose shared ownership with dynamic dispatch for complex system designs.

In this lesson, you will learn how to combine shared ownership with dynamic dispatch to store heterogeneous types behind a single reference-counted pointer.

The Problem of Heterogeneity

When you use Rc<T>, you are usually restricted to a concrete type T. But what if you have a collection of different objects that all share common behavior? For example, imagine a GUI system where you need to manage multiple Button and Slider components. If you store them in a Vec<Rc<T>>, you are forced to choose one type.

By using Trait Objects (dyn Trait), we can define a shared interface. By wrapping that trait object in Rc, we achieve shared ownership of an abstraction.

Shared Dynamic Dispatch

Instead of storing the concrete type, we store Rc<dyn Widget>. This works because Rc acts as a handle to the heap allocation, and the trait object handles the dynamic dispatch (vtable lookup) required to call methods on the underlying, unknown concrete type.

trait Widget {
    fn draw(&self);
}

struct Button;
impl Widget for Button { fn draw(&self) { println!("Drawing button"); } }

fn main() {
    // We store different types in the same collection
    let components: Vec<Rc<dyn Widget>> = vec![
        Rc::new(Button),
        // Imagine other types like Slider here...
    ];

    for component in components {
        // The Rc provides the pointer, dyn Widget provides the vtable
        component.draw();
    }
}

Why Compose Them?

The combination of Rc and dyn Trait is powerful for three reasons:

  1. Polymorphism at Scale: You can pass around a list of objects that share behavior without needing to know their specific sizes or types at compile time.
  2. Shared Lifecycle: If multiple parts of your application need to "own" a component (e.g., both the layout engine and the event handler need to point to the same Button), Rc manages the cleanup automatically.
  3. Decoupling: The code consuming the Rc<dyn Widget> doesn't need to know how the widget was allocated or what it specifically is, provided it adheres to the Widget trait.

Key Takeaway: Composing Rc with dyn Trait allows you to create collections of polymorphic objects that share a managed, heap-allocated lifetime.