Trait Objects Behind References
Using borrowed trait objects (&dyn Trait for shared access and &mut dyn Trait for mutable access) provides runtime polymorphism without any heap allocation.
Zero-Allocation Dynamic Dispatch with &dyn Trait
A common misconception is that dynamic dispatch in Rust always requires allocating a Box on the heap. When a function only needs to borrow a polymorphic value for the duration of the call, accepting &dyn Trait or &mut dyn Trait constructs the 16-byte fat pointer directly on the stack pointing to the caller's stack-allocated value:
Caller Stack Frame Callee (`process(w: &mut dyn Writer)`)
+-------------------------+ +------------------------+
| buf: StackBuffer | <────── | w.data_ptr | (No heap allocation!)
+-------------------------+ | w.vtable_ptr |
+------------------------+
trait Sink {
fn write_record(&mut self, record: &str);
}
struct ConsoleSink;
impl Sink for ConsoleSink {
fn write_record(&mut self, record: &str) {
println!("[CONSOLE] {record}");
}
}
struct VecSink<'a> {
storage: &'a mut Vec<String>,
}
impl Sink for VecSink<'_> {
fn write_record(&mut self, record: &str) {
self.storage.push(record.to_owned());
}
}
// Accepts `&mut dyn Sink` — no `Box` allocation required:
fn emit_startup_events(sink: &mut dyn Sink) {
sink.write_record("init_config");
sink.write_record("bind_socket");
}
fn main() {
let mut console = ConsoleSink;
emit_startup_events(&mut console);
let mut buffer = Vec::new();
let mut vec_sink = VecSink {
storage: &mut buffer,
};
emit_startup_events(&mut vec_sink);
println!("Buffered records: {buffer:?}");
}
[CONSOLE] init_config
[CONSOLE] bind_socket
Buffered records: ["init_config", "bind_socket"]
Conditional Stack Selection Without Box
You can also bind a local &dyn Trait reference across if / else branches when the concrete values live on the same stack frame:
use std::fmt::Display;
fn main() {
let use_hex = false;
let count = 42_u32;
let label = "forty-two";
// Both branches borrow stack values as `&dyn Display` without heap allocation:
let printable: &dyn Display = if use_hex { &label } else { &count };
println!("Selected value: {printable}");
}
Selected value: 42
Lifetime Rules for &'a (dyn Trait + 'b)
When a struct S<'b> containing a lifetime 'b is borrowed as &'a dyn Trait, the compiler requires 'b: 'a (the data inside the struct must live at least as long as the reference &'a to the trait object). By default, &'a dyn Trait elides to &'a (dyn Trait + 'a), which satisfies this automatically.