本页目录

泛型与单态化

Rust 泛型的核心机制是单态化——编译期为每种具体类型参数生成一份独立的机器码。这意味着 Vec<i32>Vec<String> 是两套完全不同的函数体,零运行时开销,代价是二进制膨胀。const generics 把常量值也纳入泛型参数,specialization 允许为特定类型提供更优实现(目前 nightly only)。

Monomorphization: 编译期为每个具体类型生成代码

Rust 泛型不是运行时的多态(没有 vtable)——编译器对每个使用泛型的具体类型生成独立的机器码:

fn max<T: PartialOrd>(a: T, b: T) -> T { if a > b { a } else { b } }

let _ = max(1, 2);                   // 编译器生成 max_i32
let _ = max(1.0, 2.0);              // 编译器生成 max_f64
let x = "a"; let y = "b";
let _ = max(x, y);                  // 编译器生成 max_&str
// 输出 binary 中有 3 份独立的 max 函数, 每份针对具体类型优化

这与 C++ 模板的机制相同——都是编译期代码生成。但在 Rust 中,泛型在定义时就被检查⁠(不需要等到实例化),所以错误信息更清晰。C++ 模板的错误通常在实例化时爆出,导致几十行的模板错误。

"零成本抽象"的含义

release build 中,泛型函数通常被内联到调用点——最终汇编中看不到泛型函数的存在,只剩几条直接操作 i32f64cmp 指令。这就是 Rust 所谓的"zero-cost abstraction":你能用高层抽象,但编译后的代码和手写针对具体类型的代码一样快。

代码膨胀与缓解

每个具体类型生成一份代码 → 大量类型组合 → 编译时间爆炸 + binary 膨胀。经典缓解策略:⁠提取非泛型 inner 函数⁠:

fn process<T: Display>(items: &[T]) {
    for item in items {
        // 泛型部分: 转成 String (每个 T 调用不同的 Display::fmt)
        let s = item.to_string();
        // 非泛型部分: 后续逻辑不依赖 T
        process_inner(&s);           // 只编译一次
    }
}
fn process_inner(s: &str) { ... }    // 不依赖 T → 不分叉

关键:把必须泛型的部分(类型转换)和可以用 trait object 或具体类型处理的部分(业务逻辑)分离。

Const Generics: 值作为泛型参数

传统泛型参数是类型。const generics (Rust 1.51+) 让也成为泛型参数——编译期常量:

struct Buffer<T, const N: usize> { data: [T; N] }

let b1: Buffer<i32, 4> = Buffer { data: [1, 2, 3, 4] };
let b2: Buffer<i32, 8> = Buffer { data: [1; 8] };
// b1 和 b2 是**不同类型**! 不能互相赋值

主要用途:固定大小的 array/vector、SIMD lanes、编译期 assert array size。

Specialization (nightly, 尚未 stable)

当前 stable Rust 不支持 trait specialization——你不能为一个 trait 提供"更具体的 impl 覆盖更泛的 impl"。这限制了某些性能优化(如为 Vec<u8> 提供比泛型 Vec<T> 更高效的 Extend impl)。nightly 上有 min_specialization 可用,但 stable 上还没有。目前替代方案是用 newtype pattern 提供特化。

参考

  • Rust Book: Chapter 10.1
  • Rust Reference: Const generics
  • RFC 2000: Const generics; RFC 1210: Specialization

Keywords: monomorphization, zero-cost abstraction, code bloat, const generics, specialization