Dynamic Dispatch with dyn Trait
Static dispatch (generics) is faster but creates code bloat. Dynamic dispatch (dyn Trait) is flexible but has a small runtime cost via virtual function tables (vtables).
trait Plugin: std::fmt::Debug {
fn name(&self) -> &str;
fn execute(&self, input: &str) -> String;
}
#[derive(Debug)]
struct UppercasePlugin;
#[derive(Debug)]
struct ReversePlugin;
#[derive(Debug)]
struct RepeatPlugin(usize);
impl Plugin for UppercasePlugin {
fn name(&self) -> &str { "uppercase" }
fn execute(&self, input: &str) -> String { input.to_uppercase() }
}
impl Plugin for ReversePlugin {
fn name(&self) -> &str { "reverse" }
fn execute(&self, input: &str) -> String { input.chars().rev().collect() }
}
impl Plugin for RepeatPlugin {
fn name(&self) -> &str { "repeat" }
fn execute(&self, input: &str) -> String { input.repeat(self.0) }
}
struct Pipeline { plugins: Vec> }
impl Pipeline {
fn new() -> Self { Pipeline { plugins: Vec::new() } }
fn add(mut self, p: Box) -> Self { self.plugins.push(p); self }
fn run(&self, input: &str) -> String {
self.plugins.iter().fold(input.to_string(), |s, p| p.execute(&s))
}
}
fn main() {
let pipeline = Pipeline::new()
.add(Box::new(UppercasePlugin))
.add(Box::new(RepeatPlugin(2)))
.add(Box::new(ReversePlugin));
println!("{}", pipeline.run("hello")); // OLLEHOLLEH
} Object Safety
// A trait is object-safe if:
// 1. Methods do not use Self as a return type (except in specific ways)
// 2. No generic methods
// 3. No associated functions without self
// Object-safe:
trait Drawable { fn draw(&self); }
// NOT object-safe (generic method):
// trait Bad { fn compare(&self, other: T) -> bool; }
// Workaround: use associated types instead of generics in traits
trait Transformer {
type Output;
fn transform(&self, input: &str) -> Self::Output;
}
// Or use trait objects with Box for mixed types
fn main() {
let items: Vec> = vec![];
// Can hold any Drawable!
} 🎯 Practice
- Create an EventHandler trait and a Vec<Box<dyn EventHandler>> event system
- Implement a plugin registry: HashMap<String, Box<dyn Plugin>> where plugins can be looked up by name
🎉 Key Takeaways
- dyn Trait stores a fat pointer: data ptr + vtable ptr
- Use Box<dyn Trait> for owned, &dyn Trait for borrowed trait objects
- Trait must be object-safe: no generic methods, no Self returns
- Use generics when types are known at compile time; dyn when they are not