跳到主要内容

零成本抽象

问题

Rust 的"零成本抽象"(Zero-Cost Abstractions)是什么意思?

答案

C++ 之父 Bjarne Stroustrup 提出的原则:

What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better.

Rust 继承了这一理念——高层抽象编译后与手写底层代码一样高效

核心示例:迭代器

// 高层抽象写法
let sum: i32 = (0..1000)
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.sum();

// 编译后与手写循环性能完全一致
let mut sum = 0i32;
for x in 0..1000 {
if x % 2 == 0 {
sum += x * x;
}
}

编译器通过内联 + 单态化将迭代器链优化为一个简单循环,没有函数调用开销、没有堆分配。

零成本抽象的实现机制

机制说明示例
单态化泛型在编译时生成具体类型的代码Vec<i32> → 专用实现
内联小函数直接展开到调用处闭包、迭代器适配器
所有权编译时决定释放时机,无运行时 GCDrop 在作用域结束时调用
trait 静态分发泛型约束在编译时解析fn foo<T: Display>(t: T)

对比:有成本的抽象

// ❌ 动态分发:有 vtable 查找开销
fn process(items: &[Box<dyn Processor>]) {
for item in items {
item.process(); // 运行时查表
}
}

// ✅ 静态分发:零成本
fn process<T: Processor>(items: &[T]) {
for item in items {
item.process(); // 编译时已确定
}
}
关键点

零成本 ≠ 零代价。单态化会增加二进制大小(code bloat),这是编译时的代价,但运行时是零成本的。


常见面试问题

Q1: 零成本抽象有什么"代价"吗?

答案

运行时零成本,但编译时有代价:

  • 编译时间增加:单态化生成大量代码
  • 二进制体积增大:每种类型组合都有独立实现
  • Trait Object 是例外:动态分发有轻微运行时开销,但可避免代码膨胀

Q2: 举例说明 Rust 与 Java/Go 在抽象成本上的区别

答案

特性RustJavaGo
泛型单态化,零成本类型擦除,装箱开销字典传递/GCShape
接口调用静态分发为主虚方法表接口值(指针对)
闭包内联展开匿名类对象函数值 + 闭包结构体
迭代器编译为循环Stream 有堆分配无对等抽象

相关链接