Trait Objects and Dynamic Dispatch
Trait Objects in Collections
Standard collections such as Vec<T> require every element to have the same compile-time size (T: Sized). To store a heterogeneous collection of different concrete types that implement a common trait, store trait object pointers (Box<dyn Trait> or &dyn Trait) inside the collection.
Memory Layout of Vec<Box<dyn Trait>>
In a Vec<Box<dyn Widget>>, the vector's contiguous buffer stores uniform 16-byte fat pointers (Box<dyn Widget>), while each fat pointer points to its own separately sized concrete struct on the heap and its corresponding vtable:
Vec<Box<dyn Widget>> Buffer (Contiguous 16-byte Fat Pointers)
+------------------------+------------------------+
| [0] data_ptr | vtable | [1] data_ptr | vtable |
+-------+----------------+-------+----------------+
| |
v v
+---------------+ +------------------------------+
| Label (24 B) | | ProgressBar (16 B) |
+---------------+ +------------------------------+
trait HealthCheck {
fn name(&self) -> &str;
fn check(&self) -> bool;
}
struct PostgresCheck {
timeout_ms: u64,
}
impl HealthCheck for PostgresCheck {
fn name(&self) -> &str {
"postgres"
}
fn check(&self) -> bool {
self.timeout_ms <= 1_000
}
}
struct RedisCheck {
cluster_mode: bool,
}
impl HealthCheck for RedisCheck {
fn name(&self) -> &str {
"redis"
}
fn check(&self) -> bool {
self.cluster_mode
}
}
fn main() {
// Heterogeneous vector holding two different concrete types:
let checks: Vec<Box<dyn HealthCheck>> = vec![
Box::new(PostgresCheck { timeout_ms: 250 }),
Box::new(RedisCheck { cluster_mode: true }),
];
for check in &checks {
println!("{:<10} healthy = {}", check.name(), check.check());
}
}
postgres healthy = true
redis healthy = true
Non-Allocating Slices of Borrowed Trait Objects (&[&dyn Trait])
When the concrete items already live on the stack and you only need to iterate over them temporarily, use an array or slice of borrowed trait objects ([&dyn Trait; N] or &[&dyn Trait]) to avoid heap allocations altogether:
use std::fmt::Display;
fn print_row(columns: &[&dyn Display]) {
let formatted: Vec<String> = columns.iter().map(|col| col.to_string()).collect();
println!("| {} |", formatted.join(" | "));
}
fn main() {
let id = 1042_u32;
let user = "alice";
let active = true;
// Coerces `[&u32, &&str, &bool]` into `&[&dyn Display]` on the stack:
print_row(&[&id, &user, &active]);
}
| 1042 | alice | true |