Trait Objects & dyn
A trait object — written dyn Trait behind a pointer like &dyn Trait or Box<dyn Trait> — lets values of different concrete types be used through one shared interface, with the right method chosen at runtime.
Learn Trait Objects & dyn in our free Rust course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Rust course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll learn how trait objects differ from generics, how to store mixed types in a Vec<Box<dyn Trait>> , the difference between static and dynamic dispatch, and the object-safety rules that decide when a trait can become a trait object.
What You'll Learn in This Lesson
1️⃣ The dyn Keyword: One Interface, Many Types
When you write &dyn Animal , you're saying "a reference to some type that implements Animal — I don't care which." This is a trait object . Unlike a concrete type, its exact identity is only known at runtime, so the call to a.name() is dispatched dynamically. The dyn keyword makes that runtime nature explicit in the type.
A single describe function handles every animal. There is exactly one copy of describe in the binary, and at each call the program follows the trait object's vtable to find the correct name and sound implementation.
2️⃣ Box<dyn Trait>: Mixing Types in a Vec
A Vec requires all its elements to be the same type, so you can't put a Circle and a Square directly in one Vec . The fix is to box each value as a trait object: Vec<Box<dyn Shape>> . Each Box<dyn Shape> owns one heap value and has a uniform pointer size, so they all fit in one vector.
Use &dyn Trait when you only need to borrow for a call, and Box<dyn Trait> when you need to own the value — in a collection, as a struct field, or as a return type that might be one of several types.
3️⃣ Static vs Dynamic Dispatch
Generics give you static dispatch : the compiler stamps out a specialized copy of the function for each concrete type (monomorphization), so calls are resolved at compile time and can be inlined. Trait objects give you dynamic dispatch : one shared function, with each call routed through a vtable at runtime. Compare the two side by side below.
Both print the same thing, but the machine code differs: greet_static is compiled twice (one version per type), while greet_dynamic exists once and looks up the method at runtime. Prefer generics for speed and single-type call sites; prefer trait objects when you need to store or pass mixed types.
Your turn. Fill in the blanks marked ___ , then run it.
Mini-Challenge: A Pluggable Notifier
Build a small notification system where many channels share one Notifier trait and live together in a Vec<Box<dyn Notifier>> . Run it with cargo run and check the output.
Common Errors (and the fix)
- ❌ fn f(x: dyn Trait) — a bare trait object has no known size. ✅ Fix: put it behind a pointer: &dyn Trait or Box<dyn Trait> .
- ❌ let v: Vec<dyn Shape> = ... — same size problem in a collection. ✅ Fix: box each element: Vec<Box<dyn Shape>> .
- ❌ E0038: cannot be made into an object — the trait isn't object safe (a method returns Self or is generic). ✅ Fix: remove the offending method or mark it where Self: Sized .
- ❌ Reaching for dyn when all values are one type — you're paying for dynamic dispatch for nothing. ✅ Fix: use a generic <T: Trait> instead.
Pro Tips
- 💡 Default to generics: they're usually faster and just as readable; reach for dyn when you truly need to mix types.
- 💡 Return Box<dyn Trait> from a factory function when the concrete type depends on runtime input.
- 💡 .as_ref() on a Box gives you a &dyn Trait to hand to functions that borrow.
📋 Quick Reference — Trait Objects
Form
Meaning
&dyn Trait
borrow any implementor
Box<dyn Trait>
own one on the heap
Vec<Box<dyn Trait>>
a list of mixed types
<T: Trait>
static dispatch (one type)
-> Box<dyn Trait>
return one of several types
Frequently Asked Questions
🎉 Lesson Complete!
- ✅ dyn Trait behind a pointer lets many concrete types share one interface
- ✅ &dyn Trait borrows; Box<dyn Trait> owns on the heap
- ✅ Vec<Box<dyn Trait>> stores mixed types behind one trait
- ✅ Generics = static dispatch (fast); trait objects = dynamic dispatch (flexible)
- ✅ Only object-safe traits can become trait objects
- ✅ Next lesson: Associated Types & Trait Bounds — typed slots inside traits
Practice quiz
What does dyn Trait represent in Rust?
- A compile-time generic
- A macro
- A trait object resolved at runtime
- A lifetime
Answer: A trait object resolved at runtime. dyn Trait is a trait object: the concrete type is known only at runtime via a vtable.
Why can't you write a bare dyn Trait by value, like fn f(x: dyn Trait)?
- Its size is not known at compile time
- dyn is reserved
- Traits cannot be parameters
- It needs a lifetime
Answer: Its size is not known at compile time. A bare trait object is unsized, so it must sit behind a pointer like &dyn Trait or Box<dyn Trait>.
Which type lets you OWN a trait object on the heap?
- &dyn Trait
- impl Trait
- dyn Trait
- Box<dyn Trait>
Answer: Box<dyn Trait>. Box<dyn Trait> owns one heap-allocated value; the box itself has a known pointer size.
How can you store mixed concrete types that share one trait in a Vec?
- Vec<dyn Trait>
- Vec<Box<dyn Trait>>
- Vec<impl Trait>
- Vec<Trait>
Answer: Vec<Box<dyn Trait>>. Each element must have the same size, so box each as Vec<Box<dyn Trait>>.
Trait objects use which kind of dispatch?
- Dynamic dispatch via a vtable
- Static dispatch via monomorphization
- No dispatch
- Inline dispatch only
Answer: Dynamic dispatch via a vtable. Each method call is looked up at runtime through the trait object's vtable.
Generics like fn f<T: Trait>(x: T) use which kind of dispatch?
- Dynamic dispatch
- Runtime vtable lookup
- Static dispatch
- Reflection
Answer: Static dispatch. The compiler stamps out a specialized copy per concrete type, resolved at compile time.
A trait object reference is described as a fat pointer because it holds...
- Two data pointers
- A data pointer plus a vtable pointer
- A length and a capacity
- A pointer and a lifetime
Answer: A data pointer plus a vtable pointer. It is two pointers wide: one to the data and one to the vtable of method pointers.
Which compiler error means a trait is not object safe?
- E0277: trait not implemented
- E0502: borrow conflict
- E0412: cannot find type
- E0038: cannot be made into an object
Answer: E0038: cannot be made into an object. E0038 fires when a trait breaks object-safety rules, such as returning Self.
Which makes a trait NOT object safe?
- A method taking &self
- A method that returns Self
- A method returning String
- A default method
Answer: A method that returns Self. Returning Self needs a known size, which a trait object lacks, so it breaks object safety.
When should you prefer a generic over dyn Trait?
- When you need mixed types in a Vec
- When the type is unknown until runtime
- When all values are one known type
- When returning one of several types
Answer: When all values are one known type. If the type is fixed, generics give static dispatch with no runtime cost.
Continue this course
- Previous: Checkpoint: Rust Basics
- Next: Associated Types & Trait Bounds