# Rust Syntax Trait Objects

> Use when the user needs `dyn Trait`, asks why a trait is not object-safe, chooses between `dyn Trait` and `impl Trait`, encounters upcasting (1.86), or uses precise capturing in trait definitions (1.87). Prevents picking `dyn Trait` when monomorphization is cheaper, missing object-safety rules, or using pre-1.86 upcasting work-arounds. Covers: `dyn Trait` type-erased dispatch, vtable representation, object-safety rules (no generic methods, no Sized self bound), `impl Trait` vs `dyn Trait` decision, trait upcasting `&dyn Sub` to `&dyn Super` (1.86), precise capturing `+ use<'a, T>` in trait definitions (1.87). Keywords: "dyn Trait", trait object, vtable, "object safety", "not object safe", E0038, "impl Trait", RPIT, "trait upcasting", "&dyn Sub", "as &dyn Super", "precise capturing", "+ use<>", "dyn dispatch", "static dispatch", "dynamic dispatch", "method resolution", "Box<dyn>", "Arc<dyn>", "&dyn", "monomorphization vs dyn", virtual.

- Skill: `impertio-studio/rust-syntax-trait-objects` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add impertio-studio/rust-syntax-trait-objects`
- Raw SKILL.md: https://api.skillmd.com/api/skills/impertio-studio/rust-syntax-trait-objects/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: Impertio-Studio (https://skillmd.com/u/impertio-studio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/impertio-studio/rust-syntax-trait-objects

---


# rust-syntax-trait-objects

Trait objects (`dyn Trait`) provide runtime polymorphism through type erasure and a vtable. They are the dynamic-dispatch counterpart to `impl Trait` (static dispatch via monomorphization). This skill covers the vtable layout, the dyn-compatibility rules formerly known as object safety, the decision between `dyn` and `impl`, trait upcasting stabilized in 1.86, and precise capturing inside trait definitions stabilized in 1.87.

## Quick Reference

### Core rules (deterministic)

1. ALWAYS use `impl Trait` when the function returns or accepts ONE concrete type per call site. NEVER use `dyn Trait` just to "be flexible"; monomorphization costs binary size but is faster at the call site.
2. ALWAYS use `dyn Trait` when storing heterogeneous values in a single collection (`Vec<Box<dyn Drawable>>`) or when a function must accept values of different concrete types at the same call site.
3. ALWAYS prefer `&dyn Trait` over `Box<dyn Trait>` when ownership transfer is not required. `Box<dyn Trait>` allocates on the heap; `&dyn Trait` is two stack words (data pointer + vtable pointer).
4. NEVER add a generic method to a trait that must remain dyn-compatible. A generic method requires a separate vtable entry per instantiation; the vtable has fixed shape, so the compiler rejects it (E0038).
5. NEVER include `where Self: Sized` on a method you want dispatched through the vtable; that bound opts the method out of the vtable, making it callable only on concrete types.
6. ALWAYS exploit trait upcasting (`&dyn Sub` to `&dyn Super`) directly in Rust 1.86+ . NEVER write an `as_supertrait()` workaround method; it was the pre-1.86 workaround and is now obsolete.
7. ALWAYS add `+ use<'a, T>` to an RPIT inside a trait definition in Rust 1.87+ when you need to constrain or exclude captured generics. NEVER rely on pre-1.87 syntax that did not allow `use<>` in trait positions.

### Decision tree: `dyn Trait` vs `impl Trait`

```
Does the function or storage need to hold values of MULTIPLE different concrete types simultaneously?
  YES -> dyn Trait (with Box, Arc, Rc, or & as appropriate pointer)
  NO  -> Is the type expressible in the signature (no generic explosion)?
          YES -> impl Trait (return position) or generic <T: Trait> (argument)
          NO  -> dyn Trait
```

```
Are you returning from a closure / iterator / async chain producing ONE inferred concrete type?
  YES -> impl Trait
Are you returning DIFFERENT concrete types from different match arms?
  YES -> Box<dyn Trait>  (or Either crate for two-arm cases)
```

### Pointer choice for `dyn Trait`

| Pointer | When to use | Cost |
|---------|-------------|------|
| `&dyn T` | Borrowed, scope-limited use, fastest | 2 words on stack, no allocation |
| `&mut dyn T` | Borrowed mutable access | 2 words on stack |
| `Box<dyn T>` | Single owner, heap-allocated | 1 heap alloc + 1 word for box ptr |
| `Rc<dyn T>` | Shared ownership, single-threaded | 1 heap alloc + atomic-free refcount |
| `Arc<dyn T>` | Shared ownership, multi-threaded (requires `T: Send + Sync` if shared across threads) | 1 heap alloc + atomic refcount |

### Dyn-compatibility rules (verbatim from the Reference)

A trait is dyn-compatible if and only if ALL of the following hold (`https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility`):

1. All supertraits MUST be dyn-compatible.
2. `Sized` MUST NOT be a supertrait. The trait MUST NOT require `Self: Sized`.
3. The trait MUST NOT have any associated constants.
4. The trait MUST NOT have any associated types with generics.
5. All associated functions MUST be either dispatchable from a trait object or be explicitly non-dispatchable.

A method is **dispatchable** if it satisfies all of:

- It has no type parameters (lifetime parameters are allowed).
- Its receiver is one of `&Self`, `&mut Self`, `Box<Self>`, `Rc<Self>`, `Arc<Self>`, `Pin<P>` where `P` is one of these.
- It does not use `Self` outside of the receiver type (no `-> Self`, no `fn foo(other: Self)`).
- It is not an `async fn` and does not return `-> impl Trait`.

A method is **non-dispatchable** if it has an explicit `where Self: Sized` bound, which omits it from the vtable but allows it to exist as a generic method on concrete types.

6. `AsyncFn`, `AsyncFnMut`, `AsyncFnOnce` are NOT dyn-compatible.

If any rule fails the compiler emits E0038 with the precise reason.

### Vtable layout (informational, not stable ABI)

```
&dyn Trait
+-----------------+
| data pointer    |  --> the value itself (any concrete type)
+-----------------+
| vtable pointer  |  --> static vtable for (Trait, ConcreteType)
+-----------------+

vtable for (Trait, ConcreteType)
+--------------------+
| destructor (drop)  |  --> ConcreteType::drop_in_place
+--------------------+
| size of value      |
+--------------------+
| align of value     |
+--------------------+
| method 0 fn ptr    |  --> ConcreteType::method_0
+--------------------+
| method 1 fn ptr    |
+--------------------+
| ...                |
+--------------------+
```

The vtable is generated once per (Trait, ConcreteType) pair, lives in static memory, and is shared by every trait object of that pairing. The data pointer is the only varying field at runtime.

## Pattern: basic `dyn Trait` storage

ALWAYS reach for `Box<dyn Trait>` when a `Vec` must hold heterogeneous implementors.

```rust
trait Drawable {
    fn draw(&self);
}

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

impl Drawable for Circle { fn draw(&self) { println!("circle r={}", self.radius); } }
impl Drawable for Square { fn draw(&self) { println!("square s={}", self.side); } }

fn main() {
    let shapes: Vec<Box<dyn Drawable>> = vec![
        Box::new(Circle { radius: 1.0 }),
        Box::new(Square { side: 2.0 }),
    ];
    for s in &shapes { s.draw(); }
}
```

## Pattern: `&dyn Trait` argument avoids allocation

NEVER allocate for a borrowed-only callsite. `&dyn Trait` is two stack words and one vtable indirection per method call.

```rust
fn render(d: &dyn Drawable) {
    d.draw();
}

let c = Circle { radius: 1.0 };
render(&c); // no Box, no heap allocation
```

## Pattern: opt-out non-dispatchable method with `where Self: Sized`

When a generic method must coexist with a dyn-compatible trait, opt it OUT of the vtable.

```rust
trait Storage {
    fn save(&self, data: &[u8]); // dispatchable, in vtable

    // NOT dispatchable; only callable on concrete types, NOT via &dyn Storage
    fn typed_save<T: serde::Serialize>(&self, value: &T) where Self: Sized {
        let bytes = serde_json::to_vec(value).unwrap();
        self.save(&bytes);
    }
}
```

This pattern keeps `&dyn Storage` valid while letting concrete impls use the generic helper.

## Pattern: trait upcasting (Rust 1.86+)

ALWAYS use direct upcasting in 1.86+ for converting `&dyn Sub` into `&dyn Super`. The release note (`https://blog.rust-lang.org/2025/04/03/Rust-1.86.0/`) : "Trait upcasting enables coercing trait object references to supertrait references." This works for `&dyn`, `&mut dyn`, `Box<dyn>`, `Rc<dyn>`, `Arc<dyn>`, and raw `*const dyn` / `*mut dyn`.

```rust
trait Animal {
    fn name(&self) -> &str;
}

trait Dog: Animal {
    fn bark(&self);
}

struct Labrador;
impl Animal for Labrador { fn name(&self) -> &str { "Buddy" } }
impl Dog for Labrador { fn bark(&self) { println!("woof"); } }

fn print_name(a: &dyn Animal) { println!("{}", a.name()); }

fn use_dog(d: &dyn Dog) {
    d.bark();
    let a: &dyn Animal = d; // upcasting in 1.86+ : implicit coercion
    print_name(a);
}
```

Pre-1.86 the standard workaround was an `as_animal(&self) -> &dyn Animal` method on `Dog`. NEVER add such a method in 1.86+ code; it is dead weight.

## Pattern: precise capturing in trait definitions (Rust 1.87+)

ALWAYS write `+ use<...>` on RPITIT (return-position `impl Trait` in trait) returns in Rust 1.87+ when generic-capture must be constrained. The 1.87 release (`https://blog.rust-lang.org/2025/05/15/Rust-1.87.0/`) lifted the restriction that prevented `use<>` in trait definitions; the syntax was already stable for free functions since 1.82.

```rust
trait Producer {
    // Captures only 'a, NOT T or other in-scope generics
    fn items<'a>(&'a self) -> impl Iterator<Item = u32> + use<'a, Self>;
}

struct Repo<T> { inner: Vec<T> }

impl<T: Copy + Into<u32>> Producer for Repo<T> {
    fn items<'a>(&'a self) -> impl Iterator<Item = u32> + use<'a, Self> {
        self.inner.iter().copied().map(Into::into)
    }
}
```

Without `use<>`, edition-2024 RPITIT captures all in-scope generics including unused ones, which can over-constrain callers. The `impl_trait_overcaptures` lint flags these and `cargo fix --edition` inserts `use<>` where preservation is needed (see `[[rust-syntax-edition-2024]]`).

## Cross-references

- `[[rust-syntax-traits]]` for trait definition syntax, blanket impls, marker traits.
- `[[rust-syntax-generics]]` for `impl Trait` argument position and generic bounds.
- `[[rust-syntax-gats]]` for generic associated types and lending iterators.
- `[[rust-syntax-lifetimes]]` for variance of `dyn Trait` in `'a` and `T`, `'static` bound, precise capturing rules.

## Reference Links

- Dyn-compatibility rules: `references/methods.md` (vtable layout + complete rule enumeration).
- End-to-end examples: `references/examples.md` (upcasting, RPITIT use<>, hot-path benchmark hint).
- Anti-patterns and fixes: `references/anti-patterns.md` (E0038 cases, allocation waste, lifetime defaulting traps).

Sources verified:

- `https://doc.rust-lang.org/reference/items/traits.html` (dyn-compatibility)
- `https://doc.rust-lang.org/reference/types/trait-object.html` (trait-object types)
- `https://doc.rust-lang.org/std/keyword.dyn.html` (the `dyn` keyword)
- `https://blog.rust-lang.org/2025/04/03/Rust-1.86.0/` (trait upcasting stabilization)
- `https://blog.rust-lang.org/2025/05/15/Rust-1.87.0/` (precise capturing in trait definitions)
- `https://blog.rust-lang.org/2024/10/17/Rust-1.82.0/` (precise capturing for free functions)
- `https://doc.rust-lang.org/error_codes/E0038.html` (object-safety errors)

