Every Rust thread about dyn Trait versus generics generates more heat than light, so I finally sat down and measured it rather than guessing. The setup: a trait with a single method that does a touch of arithmetic, called in a tight loop a million times, once through a &dyn Trait and once through a monomorphised impl Trait generic. Criterion, release mode, a warm cache.
The generic version came out ahead, as everyone insists it will, but by less than people imply. On my machine the monomorphised path ran at roughly 1.0ms and the trait-object path at about 1.3ms over the million iterations. Real, repeatable, and almost entirely down to the static path being inlinable while the vtable lookup blocks inlining and the optimiser behind it.
// monomorphised: the compiler can inline straight through
fn run<T: Compute>(items: &[T]) -> u64 {
items.iter().map(|i| i.compute()).sum()
}
// dynamic: one vtable indirection per call, no inlining
fn run_dyn(items: &[&dyn Compute]) -> u64 {
items.iter().map(|i| i.compute()).sum()
}
The thing the benchmark won't tell you is that 0.3ms over a million calls is 0.3 nanoseconds per call, and almost nothing you write is calling this in a loop tight enough to notice. If your method does any real work, touches the heap, or waits on I/O, the dispatch cost vanishes into the noise. So reach for generics where the hot path is genuinely hot, and reach for dyn where you want smaller binaries and shorter compile times and a heterogeneous collection. The performance difference is real and, for almost all of us, irrelevant.