---
title: "16.9 Trait objects: dyn Trait"
description: "Trait objects let a collection hold different types that share a trait, with the method chosen at runtime; dynamic dispatch, Box<dyn Trait>, and the tradeoff with generics."
url: "https://learnrust.net/chapter-16/trait-objects/"
last_updated: "2026-06-13"
---

# 16.9 Trait objects: dyn Trait

Generics with trait bounds (lesson [16.3](https://learnrust.net/chapter-16/trait-bounds/)) are resolved at compile time: the compiler knows each concrete type and monomorphizes a copy per type (lesson [15.5](https://learnrust.net/chapter-15/monomorphization/)). That's perfect when the types are known at compile time, but it can't express "a list holding a mix of *different* types that all share a trait." For that, Rust has **trait objects**, written `dyn Trait`, which choose the method at *runtime*. This is Rust's form of the runtime polymorphism that C++ gets from virtual functions, and it's one lesson here.

## The problem generics can't solve

Say you're building a drawing app with several shapes, each implementing a `Draw` trait, and you want a list of shapes to draw in order. The shapes are *different types*. A generic `Vec<T>` won't do: a `Vec<T: Draw>` is a vector where every element is the *same* `T`, so it could hold all circles or all squares, but not a mix. You want a vector that holds "any `Draw`-implementing thing, of possibly different types, together."

```rust
trait Draw {
    fn draw(&self) -> String;
}

struct Circle { radius: f64 }
struct Square { side: f64 }

impl Draw for Circle {
    fn draw(&self) -> String { format!("circle r={}", self.radius) }
}
impl Draw for Square {
    fn draw(&self) -> String { format!("square s={}", self.side) }
}
```

## Box<dyn Trait> holds a mix

A **trait object** is the answer. `Box<dyn Draw>` means "a heap pointer to some value that implements `Draw`, exact type not known until runtime" (`Box` is a heap pointer, chapter 21; for now read `Box<dyn Draw>` as "some `Draw`-thing"). A `Vec<Box<dyn Draw>>` can hold circles and squares side by side:

```rust
fn main() {
    let shapes: Vec<Box<dyn Draw>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Square { side: 3.0 }),
        Box::new(Circle { radius: 1.0 }),
    ];

    for shape in &shapes {
        println!("{}", shape.draw());
    }
}
```

```
circle r=2
square s=3
circle r=1
```

The vector mixes `Circle` and `Square`, because each is boxed as a `dyn Draw` that erases the specific type and keeps only "this implements `Draw`." When the loop calls `shape.draw()`, the program looks up *at runtime* which `draw` to call based on the actual type behind each box. This is **dynamic dispatch**: the method to call is decided while the program runs, not while it compiles.

> **Key insight**
>
> The `dyn` keyword marks the line between Rust's two kinds of polymorphism. Generics (`<T: Trait>`) are **static dispatch**: the compiler knows the type and bakes in the exact method call, with a specialized copy per type (fast, larger binary). Trait objects (`dyn Trait`) are **dynamic dispatch**: one shared copy of the code, and the method is looked up at runtime (slightly slower per call, smaller binary, and able to hold mixed types). Reach for generics by default; reach for trait objects when you genuinely need a collection of mixed types behind a shared trait, or to choose a type at runtime.

## Static versus dynamic dispatch, the tradeoff

This is the other side of the monomorphization coin from lesson [15.5](https://learnrust.net/chapter-15/monomorphization/). With generics, the compiler stamps out a copy of the code for each type, so calls are direct and as fast as hand-written code, at the cost of binary size and the inability to mix types. With trait objects, there's one copy of the code, and each call goes through a small runtime lookup (a *vtable*, see the box) to find the right method. The per-call cost is tiny and rarely matters, but it's not zero, which is why generics are the default and trait objects are the tool for when you specifically need their flexibility: heterogeneous collections, or returning different types from different branches (the limitation lesson [16.4](https://learnrust.net/chapter-16/impl-trait/) ran into, which `Box<dyn Trait>` solves).

> **For advanced readers**
>
> Dynamic dispatch works through a **vtable** (virtual method table): a small table of function pointers, one per trait method, that each trait object carries alongside its data pointer. `shape.draw()` follows the data pointer to the value and the vtable pointer to the right `draw`. This is the same mechanism as C++ virtual functions, just opt-in (you write `dyn`) rather than triggered by a keyword on the class. There's also a rule called *object safety*: only traits whose methods can work through a vtable can become trait objects (roughly, methods must take `self` by reference and not be generic). You'll meet it as an error message ("the trait cannot be made into an object") rather than needing to memorize the rules; the compiler tells you when a trait isn't object-safe and usually why.

## &dyn Trait too

You don't always need `Box`. A function can take `&dyn Trait`, a reference to a trait object, when it just needs to call trait methods on something without owning it:

```rust
fn describe(item: &dyn Draw) {
    println!("drawing: {}", item.draw());
}
```

`describe` accepts a reference to any `Draw` implementer, using dynamic dispatch. This is the borrowing version (lesson [9.1](https://learnrust.net/chapter-9/references-borrowing-a-value/)) and avoids the heap allocation `Box` implies. Use `&dyn Trait` for "borrow some trait-implementing thing for this call," and `Box<dyn Trait>` when you need to *own* and *store* trait objects, as in the mixed `Vec`.

## Quiz time

**Question #1**

Why can't a `Vec<T: Draw>` hold both a `Circle` and a `Square`, and what can?

<details class="solution">
<summary>Show solution</summary>

A generic `Vec<T>` requires every element to be the *same* type `T`, so it's all circles or all squares, not a mix. A `Vec<Box<dyn Draw>>` (a vector of trait objects) can hold both, because each element is "some `Draw`-implementing thing" with its concrete type erased, allowing different types together.

</details>

**Question #2**

What's the difference between static dispatch (generics) and dynamic dispatch (`dyn Trait`)?

<details class="solution">
<summary>Show solution</summary>

Static dispatch (generics with trait bounds) resolves the exact method at compile time and monomorphizes a specialized copy per type: fast calls, larger binary, types fixed at compile time. Dynamic dispatch (`dyn Trait`) keeps one copy of the code and looks up the method at runtime via a vtable: a tiny per-call cost, smaller binary, and the ability to hold mixed types or choose types at runtime. Generics are the default; trait objects are for when you need their flexibility.

</details>

**Question #3**

When would you use `&dyn Trait` versus `Box<dyn Trait>`?

<details class="solution">
<summary>Show solution</summary>

`&dyn Trait` borrows a trait-implementing value for the duration of a call (no ownership, no heap allocation), good for a function that just needs to call trait methods on something. `Box<dyn Trait>` owns the value on the heap, needed when you must *store* trait objects, such as in a `Vec<Box<dyn Draw>>` that keeps a mixed collection alive.

</details>

Trait objects are how Rust does runtime polymorphism without inheritance. That raises the question C++ veterans will be asking through this whole chapter: if there's no inheritance, how do you reuse code and model "is-a" relationships? The last lesson answers it directly.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
