2021年,一位同事告诉我:Rust是"能在bug诞生前就消灭它的语言"。三年后,我在Hacker News看到一个648赞的热帖,标题叫《Rust抓不住的bug》。我打开自己的三个生产项目——一个纯Rust,两个依赖Rust工具链——逐行核对。结果找到了他们保证不会存在的那些bug。

一个编译通过但逻辑崩塌的例子

打开网易新闻 查看精彩图片

我的配置处理工具里有一段滑动窗口代码。Rust编译器毫无怨言地通过了它:没有越界访问,没有空指针,内存安全满分。但业务逻辑是错的——窗口边界条件处理与需求文档不符。

编译器检查的是"内存是否安全",不是"代码是否正确"。这个区别,我的代码库用具体数字说话。

为什么内存安全不等于程序正确

原帖把bug分为四类。我在自己的代码里找到了对应实例,每一类都指向同一个盲区:Rust的承诺有明确边界,但社区讨论时常模糊这个边界。

第一类是算术溢出。Rust在debug模式会panic,release模式默认回绕(wrap around)。我的图像处理依赖库里,一个坐标计算在release构建中静默产生错误结果。没有崩溃,没有日志,只有错误的像素位置。

第二类是并发逻辑错误。Rust防止数据竞争,但不防止竞态条件。我的异步任务调度代码里,两个任务通过消息通道协调,编译器确认了所有权安全,但时序逻辑导致偶发的状态不一致。测试覆盖率90%没抓到,生产环境每周出现两三次。

第三类是业务逻辑错误。前面提到的滑动窗口属于此类。最隐蔽的是第四类:与unsafe代码或FFI边界相关的假设违反。我的项目依赖的某个Rust库封装了C代码,文档承诺"输入已校验",但我的调用方没校验。编译器无法跨越这个抽象边界。

TypeScript给我的同一课

去年测试TypeScript 7 beta时,我花了最多时间调试的不是类型错误,而是"类型正确但语义错误"的代码。编译器热情接受的代码,实际行为与意图相反。

Rust和TypeScript在这个模式上惊人相似:它们消除了特定类别的错误,但可能制造一种虚假安全感——让人误以为"编译通过=正确实现"。我的代码审计显示,生产环境中耗时最长的bug,70%属于"编译器无法看见"的范畴。

社区话语与实际体验的落差

我对Rust社区有真正的尊重。borrow checker是工程杰作,生命周期系统解决了C++几十年的痛点。但"消除bug"的营销话术需要拆解:它消除的是内存安全bug,不是逻辑bug,不是并发设计bug,不是业务规则bug。

我的三个项目代码量合计约4.2万行Rust。审计发现的12个有效bug中,3个是内存相关(确实被Rust阻止),9个是逻辑/设计/假设类(Rust未介入)。这个比例与Hacker News原帖的社区反馈基本吻合。

一位维护者在我的issue下回复:「Rust保证的是当你说'我要访问这块内存'时,编译器确认你真的可以。它不保证你说的是对的。」

这对技术选型意味着什么

我不后悔用Rust。内存安全bug的修复成本通常高于逻辑bug,Rust在这个维度兑现了承诺。但需要校准预期:如果团队的问题是"代码经常崩溃",Rust是解药;如果问题是"业务逻辑复杂容易出错",需要其他工具——更完善的测试、形式化验证、或更严格的代码审查。

我的配置处理工具现在增加了property-based testing。图像处理依赖库启用了overflow-checks=true强制panic。异步调度模块重写了状态机,用状态类型替代布尔标志。这些改进与Rust无关,与"承认编译器的边界"有关。

那个2021年的同事现在也在用Rust。上周聊天时他说,当年那句话应该加上后半句:"它消灭一类bug,让你能专注于另一类。"

三年后的我发现,后一类bug的数量和破坏力,被低估了。

你的代码库里,编译器看不见的bug占比多少?