「三年前我们写数组的方式,全是错的。」一位密码学库维护者在内部复盘时这样写道。这不是事后诸葛亮——直到2021年3月Rust 1.51发布,他们才知道自己被迫维护的16份复制粘贴代码,本可以只用一份。
这个团队的故事,是常量泛型(const generics)最直观的商业案例:编译期精度如何转化为运行时性能,以及为什么"零成本抽象"不是口号而是可量化的工程决策。
![]()
问题:16份代码,16倍bug
他们的密码学库有个秘密——不是安全漏洞,是"不好意思在例会上提"的实现细节。
矩阵乘法有16个几乎一模一样的实现。不同尺寸,相同逻辑,用宏批量生成。代码量8347行,二进制膨胀340KB,编译慢到让人泡茶。最痛苦的是修bug:改一处,得手动同步16处。
根源很直接:Rust的类型系统允许泛型参数化类型,但不允许参数化数组长度。
他们的网络协议解析器更夸张。解析16字节头部?一个函数。32字节?复制粘贴改数字。64字节?再来一遍。团队真的写了16个这样的函数,逻辑完全相同,只是硬编码的尺寸不同。
「我不是在开玩笑,我们真的干了这事。」
转折点:2021年3月的那个稳定版本
Rust 1.51的发布说明里,常量泛型 stabilization 只占几行。但对这支团队,这是架构重写许可证。
常量泛型的核心能力:让类型参数化依赖于常量值,通常是整数。此前合法的代码:
// 对类型T泛型——一直都可以
struct Container {
data: Vec // T可以是任何类型
}
此前被拒绝的代码:
// 对尺寸SIZE泛型——Rust 1.51之前不行
struct FixedArray {
data: [T; N] // 编译器会喊:"N不是类型!"
}
区别看起来微小。但前者只是"装东西的容器",后者是"编译期已知尺寸的数组"——这直接决定了能否消除动态分配、能否让编译器生成SIMD指令。
重写之后:数字说话
团队用常量泛型重写后的结果:
• 代码量:从8347行降至约1400行(降幅83%)
• 二进制体积:减少340KB的宏膨胀代码
• 运行时性能:提升83%
性能 gains 的来源很具体。不是魔法,是两个此前被阻塞的优化:
第一,消除动态分配。数组尺寸编译期确定,栈分配代替堆分配。第二,编译器终于能正确向量化。之前宏生成的代码太复杂,SIMD优化器无法穿透;常量泛型让类型系统足够透明,自动向量化生效。
重写后的解析函数变成这样:
// 之后:一个函数,任意尺寸(终于!)
fn parse_header(
data: [u8; N] // N在编译期检查
) -> Header { ... }
16个函数合并为1个。修bug只需改一处。新协议支持新尺寸?加一行调用,不用复制粘贴。
为什么这件事值得现在关注
常量泛型稳定已四年,但理解其工程价值的案例仍不多。这个密码学库的迁移提供了可量化的参照:
83%代码删除率、83%性能提升、340KB二进制瘦身——这些数字来自"被迫用宏 workaround 三年"的真实债务。如果你维护的Rust代码库还在用宏生成不同尺寸的数组处理逻辑,这笔技术债的利息正在累积。
更深层的影响是设计模式的转变。常量泛型让"尺寸作为类型的一部分"成为一等公民,这意味着API可以在编译期拒绝非法尺寸组合,而不是运行时panic。对密码学、网络协议、嵌入式系统这类尺寸敏感领域,这是从"约定"到"强制"的质变。
团队最后留下一句话:「不是宏不好,是我们用错了地方。常量泛型把该在编译期做的事,还给了编译期。」
8347行到1400行。340KB到零。16个bug修复点到1个。这些数字定义了什么是"零成本抽象"——不是没成本,是成本发生在正确的阶段。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.