直接回答:泛型 fn f<T: Trait>(x: T) 编译期为每个用到的具体类型生成一份单态化代码,调用直接静态绑定,可内联、零虚表开销,但代码体积膨胀、编译变慢,且一次只能装一种类型。dyn Trait 走虚表运行时间接调用,一次调用多一条跳转、挡住内联,但换来异构集合(Vec<Box<dyn Trait>> 装不同类型)、更小二进制、稳定 ABI 和插件式架构。默认先用泛型,需要异构或运行期可替换时上 dyn。
展开解析:决策的几个信号:集合里要塞多种实现(多个后端、插件列表)→ 只能 dyn;性能热点上的小调用(迭代器、比较器)→ 泛型,内联收益大;库希望下游类型参与深度优化 → 泛型;想控制编译时间和二进制体积 → dyn。混合手法常见:内部泛型、边界处擦除成 dyn(Box::new(concrete) as Box<dyn Trait>),std 的 io::Read 生态就这么用。dyn 的代价要具体看:vtable 间接一次约几纳秒,只有被调用百万次的热点才值得在意。反向坑也真实存在:全泛型代码编译慢、错误信息难看、trait 约束层层传染,大型项目常在有意识的位置做类型擦除。
追问方向:vtable 里存了什么?为什么 dyn Trait 要求对象安全?impl Trait 在参数和返回值位置的语义差异?
(约 450 字)