The advice is always "prefer generics, the vtable lookup costs you". I'd repeated it myself without ever measuring it, which is a bad habit, so I sat down with criterion and a small hot loop to see how big the gap really is.
The workload was deliberately trivial: a trait with one method doing a tiny bit of arithmetic, called a million times. The generic version monomorphises and inlines; the &dyn Trait version goes through a vtable each call.
fn run_static<T: Op>(op: &T, xs: &[u64]) -> u64 {
xs.iter().map(|&x| op.apply(x)).sum()
}
fn run_dyn(op: &dyn Op, xs: &[u64]) -> u64 {
xs.iter().map(|&x| op.apply(x)).sum()
}
On my machine the generic path came out a touch over twice as fast for this pathological case. That sounds damning until you notice the absolute numbers are nanoseconds, and the gap collapses the moment the method does anything real, because the indirect call stops being the bottleneck.
So the rule survives, but with a caveat I now actually believe: reach for generics in genuine hot paths, and stop feeling guilty about dyn everywhere else. It buys you smaller binaries and saner compile times, and the cost is noise unless you've measured otherwise.