A Didactic dyn Trait Example
https://gist.github.com/rust-play/83b8b2bde4da2e392c42aab4482394d5

error[E0308]: mismatched types
a Vec<T> must have one concrete type
Try generics:
fn run_all<T: Action>(actions: Vec<T>) {
for a in actions {
a.run();
}
}
This works only if all elements are the same concrete type.
Generics require:
- Knowing all concrete types at compile time
- One monomorphized function per type
But here:
- Types are mixed
- Stored together
- Chosen at runtime
This is runtime polymorphism, not compile-time polymorphism.
But our problem is:
We want different concrete types in the same collection.
Generics cannot express:
“Many types, same interface, chosen at runtime”
When you SHOULD use dyn Trait
This example captures the canonical use case:
✅ Heterogeneous collections
✅ Runtime plugin systems
✅ Event handlers / callbacks
✅ Command pattern
✅ Dependency injection boundaries
Enter dyn Trait (this is the moment)
let actions: Vec<Box<dyn Action>> = vec![
Box::new(PrintAction),
Box::new(SaveAction),
];
Why this works:
Box<dyn Action>has a fixed size- Each element is a fat pointer:
- data pointer
- vtable pointer
- The vector doesn’t care what the concrete type is
Using dyn Trait is a compile-time design decision that enables runtime polymorphism, not a runtime workaround.
Where the choice is made
The choice happens here:
Vec<Box<dyn Action>>
That type annotation:
- Forces heap allocation
- Forces fat pointers
- Forces vtables
- Forces dynamic dispatch
main is just where the decision is written, not where it’s executed.
How Rust solves this
Rust says:
“You may use
dyn Trait, but only behind something with a known size.”
That “something” is a pointer, e.g.:
&dyn Action
Box<dyn Action>
Arc<dyn Action>
These all have a known, fixed size.
