Trait Objects and dyn
Dynamic dispatch.
Trait Objects and dyn is a free Learn Rust Coding lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Learn Rust Coding learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Problem: Mixed Types
Generics with trait bounds work great when each call uses one concrete type. But what if you want a collection of different types that all share a trait? Generics cannot hold a mix.
Trait objects solve this.
What Is a Trait Object?
A trait object is a value referred to through a pointer plus the keyword dyn, like &dyn Trait or Box<dyn Trait>.
It lets you treat many concrete types uniformly at runtime.
A Box of dyn Trait
Box<dyn Trait> owns a heap value of some type implementing the trait. You call trait methods without knowing the concrete type.
trait Speak { fn say(&self) -> String; }
struct Dog;
impl Speak for Dog { fn say(&self) -> String { String::from("Woof") } }
fn main() {
let animal: Box<dyn Speak> = Box::new(Dog);
println!("{}", animal.say());
}A Vec of Trait Objects
The big payoff: a single vector can hold many different types as long as each implements the trait.
trait Speak { fn say(&self) -> String; }
struct Dog;
struct Cat;
impl Speak for Dog { fn say(&self) -> String { String::from("Woof") } }
impl Speak for Cat { fn say(&self) -> String { String::from("Meow") } }
fn main() {
let zoo: Vec<Box<dyn Speak>> = vec![Box::new(Dog), Box::new(Cat)];
for animal in &zoo {
println!("{}", animal.say());
}
}Dynamic Dispatch
With trait objects, the method to call is chosen at runtime by looking it up in a hidden table (the vtable). This is called dynamic dispatch.
The cost is a small indirection; the benefit is runtime flexibility.
Functions Returning Trait Objects
A function can return Box<dyn Trait> when the concrete type varies. This is useful for factory functions that decide the type at runtime.
trait Speak { fn say(&self) -> String; }
struct Dog;
struct Cat;
impl Speak for Dog { fn say(&self) -> String { String::from("Woof") } }
impl Speak for Cat { fn say(&self) -> String { String::from("Meow") } }
fn make(kind: &str) -> Box<dyn Speak> {
if kind == "dog" { Box::new(Dog) } else { Box::new(Cat) }
}
fn main() {
println!("{}", make("cat").say());
}Borrowed Trait Objects
You can also pass a borrowed trait object with &dyn Trait when you do not need ownership. No heap allocation is involved.
trait Speak { fn say(&self) -> String; }
struct Dog;
impl Speak for Dog { fn say(&self) -> String { String::from("Woof") } }
fn announce(s: &dyn Speak) {
println!("heard: {}", s.say());
}
fn main() {
let d = Dog;
announce(&d);
}Object Safety
Not every trait can be a trait object. A trait must be object safe: roughly, its methods cannot return Self by value or use generic type parameters.
Methods that take &self and use concrete types are fine.
An Object-Unsafe Example
A trait with a method returning Self cannot be used as dyn, because the size of Self is unknown behind a pointer. Keep trait-object traits simple.
A Plugin-Style Example
Trait objects shine for plugin systems: store a list of handlers behind dyn and run them all without caring about concrete types.
trait Task { fn run(&self) -> i32; }
struct Add { a: i32, b: i32 }
struct Negate { x: i32 }
impl Task for Add { fn run(&self) -> i32 { self.a + self.b } }
impl Task for Negate { fn run(&self) -> i32 { -self.x } }
fn main() {
let tasks: Vec<Box<dyn Task>> = vec![Box::new(Add { a: 2, b: 3 }), Box::new(Negate { x: 7 })];
let total: i32 = tasks.iter().map(|t| t.run()).sum();
println!("total {}", total);
}When to Reach for dyn
Use trait objects when you need heterogeneous collections, runtime-chosen types, or to reduce code bloat from many generic instantiations.
Otherwise prefer generics for maximum speed.
Quick Check
Test your grasp of trait objects.
Recap
You learned dynamic dispatch:
- Trait objects use
dynbehind a pointer (Box<dyn T>,&dyn T) - They allow heterogeneous collections and runtime-chosen types
- Method calls go through a vtable (dynamic dispatch)
- Traits must be object safe to be used this way
Frequently asked questions
Is the “Trait Objects and dyn” lesson free?
Yes — the full text of “Trait Objects and dyn” is free to read here on the web, and the Learn Rust Coding course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Learn Rust Coding course, upgrade to CoddyKit PRO.
What will I learn in “Trait Objects and dyn”?
Dynamic dispatch. You practise Learn Rust Coding with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Learn Rust Coding?
No prior experience is required. Learn Rust Coding on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Trait Objects and dyn” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Learn Rust Coding lesson?
Yes. Every Learn Rust Coding lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Defining Traits
- Trait Objects and dyn
- Static vs Dynamic Dispatch
- Default Methods