Introduction
When you need heterogeneous collections or runtime polymorphism, trait objects (dyn Trait) provide dynamic dispatch. Since Rust 1.86, you can also upcast trait objects to their supertraits, making dynamic dispatch even more flexible.
Key Concepts
- Trait Object: A fat pointer (
&dyn TraitorBox<dyn Trait>) that pairs a data pointer with a vtable pointer. - Vtable: A table of function pointers generated by the compiler for each concrete type implementing the trait.
- Dyn Compatibility: The set of rules determining whether a trait can be used as a trait object (formerly called "object safety").
- Trait Upcasting: Coercing
&dyn SubTraitto&dyn SuperTrait, stabilized in Rust 1.86.
Real World Context
GUI frameworks store heterogeneous widget collections as Vec<Box<dyn Widget>>. Plugin systems use dyn Plugin to load implementations at runtime. Trait upcasting simplifies working with trait hierarchies and is especially useful with dyn Any.
Deep Dive
Trait objects let you mix different types behind a common interface:
rusttrait Animal { fn speak(&self) -> &str; } struct Dog; struct Cat; impl Animal for Dog { fn speak(&self) -> &str { "Woof!" } } impl Animal for Cat { fn speak(&self) -> &str { "Meow!" } } let animals: Vec<Box<dyn Animal>> = vec![Box::new(Dog), Box::new(Cat)]; for a in &animals { println!("{}", a.speak()); // Dynamic dispatch via vtable }
Each dyn Trait value is a fat pointer containing a pointer to the data and a pointer to the vtable. The vtable holds the drop function, size, alignment, and pointers to each trait method.
Trait Upcasting (Rust 1.86+)
You can now coerce a trait object to any of its supertraits:
rustuse std::any::Any; use std::fmt::Debug; trait MyTrait: Debug + Any { fn name(&self) -> &str; } fn print_debug(obj: &dyn MyTrait) { let debug: &dyn Debug = obj; // Upcast to supertrait println!("{:?}", debug); let any: &dyn Any = obj; // Upcast to Any if let Some(s) = any.downcast_ref::<String>() { println!("It's a string: {s}"); } }
Dyn Compatibility Rules
Not all traits can be used as trait objects. A trait is dyn-compatible when:
- It has no
Self: Sizedsupertrait bound - It has no associated constants
- All methods have a
selfreceiver (&self,&mut self,Box<Self>, etc.) - No method returns
Self(without awhere Self: Sizedescape hatch) - No method has generic type parameters
- No method returns
-> impl Traitor isasync fn(these are not dyn-compatible)
rust// Dyn-compatible trait Drawable { fn draw(&self); } // NOT dyn-compatible: returns Self trait Duplicatable { fn duplicate(&self) -> Self; } // NOT dyn-compatible: generic method trait Processor { fn process<T>(&self, val: T); }
Common Pitfalls
- Forgetting dyn compatibility rules — Adding a generic method or associated constant to a trait makes it unusable as
dyn Trait. Usewhere Self: Sizedon non-dispatchable methods to keep the trait dyn-compatible. - Unnecessary boxing — Using
Box<dyn Trait>when&dyn Traitsuffices wastes a heap allocation.
Best Practices
- Default to static dispatch — Use generics unless you specifically need heterogeneous collections or runtime polymorphism.
- Use trait upcasting with
Anyfor type erasure patterns — Since Rust 1.86, you can upcast todyn Anyand downcast to recover the concrete type.
Summary
dyn Traitenables runtime polymorphism via fat pointers and vtables.- Trait upcasting (Rust 1.86) lets you coerce
&dyn SubTraitto&dyn SuperTrait. - Dyn compatibility rules restrict which traits can be used as trait objects.
async fnand-> impl Traitmethods are not dyn-compatible.
Code Examples
// Different ways to hold trait objects
trait Draw {
fn draw(&self);
}
struct Circle;
impl Draw for Circle { fn draw(&self) { /* ... */ } }
// Owned trait object (heap-allocated)
let shape: Box<dyn Draw> = Box::new(Circle);
// Borrowed trait object
let shape_ref: &dyn Draw = &Circle;
// Shared ownership trait object
use std::sync::Arc;
let shape_shared: Arc<dyn Draw> = Arc::new(Circle);
// Factory pattern returning trait objects
fn create_shape(kind: &str) -> Box<dyn Draw> {
match kind {
"circle" => Box::new(Circle),
_ => Box::new(Circle),
}
}