Trait Objects and Unsized Values
A trait object (dyn Trait) is an unsized type representing any concrete value that implements Trait whose static type has been erased behind a fat pointer (&dyn Trait, &mut dyn Trait, Box<dyn Trait>, Rc<dyn Trait>, or Arc<dyn Trait>).
Trait Object Lifetime Bounds (dyn Trait + 'a)
Because a concrete type erased inside dyn Trait might itself hold borrowed references (such as struct View<'a>(&'a str) implementing Display), every trait object type has a trait-object lifetime bound (dyn Trait + 'a) that specifies the minimum lifetime for which the underlying concrete value is valid:
Context of dyn Trait |
Default Elided Lifetime Bound | Meaning |
|---|---|---|
Box<dyn Trait> |
Box<dyn Trait + 'static> |
Concrete type inside Box cannot contain non-'static references (unless written Box<dyn Trait + 'a>) |
&'a dyn Trait |
&'a (dyn Trait + 'a) |
Concrete type inside &'a only needs to live at least as long as 'a |
&'a mut dyn Trait |
&'a mut (dyn Trait + 'a) |
Concrete type only needs to live at least as long as 'a |
Arc<dyn Trait> / Rc<dyn Trait> |
Arc<dyn Trait + 'static> |
Defaults to 'static unless overridden (Arc<dyn Trait + 'a>) |
Why `Box<dyn Trait>` Defaults to `'static`:
If `local_str` lives for `'a`, wrapping `View(&local_str)` in `Box<dyn Display>`
requires writing `Box<dyn Display + 'a>` (or `Box<dyn Display + '_>`) so the
borrow checker prevents the `Box` from outliving `local_str`!
use std::fmt::Display;
struct BorrowedBanner<'a> {
text: &'a str,
}
impl Display for BorrowedBanner<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "=== {} ===", self.text)
}
}
// `+ 'a` (or `+ '_`) allows `Box<dyn Display + '_>` to hold a type borrowing `input`:
fn make_boxed_banner(input: &str) -> Box<dyn Display + '_> {
Box::new(BorrowedBanner { text: input })
}
fn main() {
let local = String::from("Scoped Trait Object");
let banner = make_boxed_banner(&local);
println!("{}", banner);
}
=== Scoped Trait Object ===
Combining Trait Objects with Auto-Traits (dyn Trait + Send + Sync)
In Rust, a trait object type can specify at most one non-auto base trait (plus its supertraits), alongside any number of auto-traits (Send, Sync, Unpin, UnwindSafe):
- Valid:
Box<dyn Write + Send + Sync + Unpin> - Invalid (
E0225):Box<dyn Read + Write>(becauseReadandWriteare both regular vtable-bearing traits, and a fat pointer only has room for one vtable pointer!).
To combine two regular traits into a single trait object, define a subtrait that combines both as supertraits (trait ReadWrite: Read + Write {}) and add a blanket implementation (impl<T: Read + Write> ReadWrite for T {}):
use std::fmt::{Debug, Display};
// Combine `Display` and `Debug` into a single vtable via a supertrait + blanket impl:
trait DisplayAndDebug: Display + Debug {}
impl<T: Display + Debug> DisplayAndDebug for T {}
fn main() {
let items: Vec<Box<dyn DisplayAndDebug + Send + Sync>> = vec![
Box::new(42_i32),
Box::new(String::from("hello")),
];
for item in &items {
println!("Display: {:<8} | Debug: {:?}", item, item);
}
}
Display: 42 | Debug: 42
Display: hello | Debug: "hello"