首页 / 文章

从Bun的AI驱动Zig转Rust项目中学到的经验:验证才是真正的关键工作

Bun的代理协助其从Zig迁移到Rust这一举措,揭示了将测试套件视为契约、移植指南与不安全代码之间的关系,以及为何当前验证机制会限制人工智能编程的发展。

发布者:REACTAPP.TOP阅读约 12 分钟2857 词

来源: Bun Rewrote Its Runtime in Rust With AI. The Real Story Is Bigger Than the Code

大多数工程团队都尽量避免对大型生产系统进行语言重写。据报道,JavaScript和TypeScript的运行时及工具包Bun已将其实现从Zig迁移到Rust,其中大部分代码转换工作由AI编码工具完成,其成果数据十分惊人。不过更值得关注的是,关键不在于这些工具生成了多少代码,而在于后续流程是如何判断这些代码中的哪些是可信赖的。本文将详细介绍这一流程,并将其转化为可用于自身AI辅助重构和迁移的实际方法。

为何通常会避免重写

语言重写总是伴随着一系列众所周知的风险。它可能会引发细微的退化,数月间无人察觉;使性能朝意想不到的方向变化;破坏仅有少数用户使用的平台;还会占用团队大量精力。最糟糕的情况下,团队不得不同时维护旧版本和新版本,每个漏洞修复都需要付出双倍成本。

正因如此,Bun 的迁移才备受关注。在撰写本文时,该项目的代码仓库将 Bun 描述为基于 Rust 且建立在 JavaScriptCore 之上的 JavaScript 运行时,而 Anthropic 宣布将在 2025 年 12 月收购 Bun。公开报道指出,此次移植工作大约在 11 天内完成了 535,000 行 Zig 代码的转换,由自动化工具在严格定义的流程中承担了大部分实现工作。

很容易将此视为又一篇“AI编写了大量代码”的标题新闻。更值得探讨的问题是:当人类建立起完善的机制来验证结果是否正确时,AI系统能否实现重大变革?对于未来几年软件的开发方式而言,这个问题更为重要。

移植而非重新设计

Bun最初主要是用Zig语言编写的,这是一种以低级控制能力和可预测的性能而著称的系统编程语言。这次迁移并非被视为全新的开始,其目标十分明确:保持现有的架构和可观察的行为,仅更改实现代码所使用的语言。

这一设计思路正是关键的设计决策。传统的重写方式会促使工程师在过程中不断重新设计,包括更换数据结构、调整模块顺序、添加抽象层以及修改API。每一个这样的选择都会带来新的风险,也需要重新审查。通过排除大部分此类可能性,Bun团队将一项开放式的创造性任务转变成了具有明确参照标准的机械性工作:即当前的实现代码以及描述它的测试用例。

行数似乎是最困难的部分,但实际上并非如此。编写语法正确的Rust代码相对容易,真正的难点在于确保该Rust代码的行为与它所替代的Zig代码完全一致。

测试套件作为行为契约

想象一下,某个智能体收到的指令仅仅是“用 Rust 重写这个代码库”。这样的指示让所有重要决策都悬而未决:哪些部分可以修改,哪些必须保持不变?哪些捷径是可以接受的?哪些内存行为是关键所在?最重要的是,如何才能知道输出结果是正确的?

Bun 对最后一个问题已经有了答案:一套庞大且成熟的测试套件。一旦将这些测试视为一份契约,智能体就不再需要自行设计实现方案,而是只需复现那些已经以可执行形式记录下来的行为,它生成的每个文件要么符合该契约,要么不符合。

测试能够和无法承诺什么

测试套件并不能证明代码的正确性。它仅能检测那些开发者认为需要实现的功能,而经过转换的文件即便在所有测试中都通过,仍可能在未被测试过的路径上存在差异。测试真正提供的是一种反馈机制,其效果远优于让模型自行评判自己的输出。没有测试的话,生成的改写内容可能看起来完全合理,但实际上却在悄悄改变语义;而有测试存在时,每一个生成的改动都必须证明其合理性。

Bun 工作流设计了多层审核机制:编译、测试套件检测、各支持平台的验证、代码审查以及进一步确认。模型负责生成候选方案,而工程团队则决定哪些方案能够通过验证。这种分工模式正是需要重点理解的核心思想。

为何移植指南与模型同样重要

据称,在智能体接触数千个文件之前,该项目就编写了一份详细的移植指南,说明了如何在 Rust 中表达 Zig 的结构。该指南涵盖了类型映射、命名规范、常见的转换模式、依赖项的处理方式,以及对智能体行为范围的明确限制。

这样做的效果是将模糊的请求(“用 Rust 编写这个文件”)转化为有明确约束的请求:按照约定的映射规则转换文件,保持其可观察的行为不变,同时不改变其架构。在这种环境下,智能体的表现会更好,因为可行答案的范围较小且定义清晰。

指南还有助于进行审查。与其提出模糊的问题“这样看起来行吗?”,审查者可以提出两个具体的问题:

  1. 这段 Rust 代码的行为是否与它所替代的 Zig 代码相同?
  2. 转换工具是否遵循了项目约定的翻译规则?

这两个问题都有可以验证的答案,而这正是当变更量巨大时审查流程所需要的。

一份实用的移植指南应包含哪些内容

如果您计划进行类似的迁移,哪怕规模小得多,这类指南通常也会详细说明:

  • 每种源代码类型、容器及错误处理机制如何映射到目标语言
  • 命名规则,以确保生成的代码在各个文件及转换工具中保持一致
  • 哪些依赖可以被引入,哪些是被禁止的
  • 即使某些写法能让文件通过编译,转换工具也不得使用的模式
  • 对于单个文件或模块而言,什么才算“完成”

这一更广泛的教训不仅适用于Bun:围绕智能体的指令系统几乎能与模型选择一样影响最终结果的质量。那些已经维护智能体指令文件的团队会认出这种模式;我们关于编写高效CLAUDE.md文件的指南也涵盖了日常工作中相同的原理。

忠实于原代码的Rust并不总是符合Rust惯用写法的

那些期望移植后的代码看起来像从头开始编写的Rust代码的人会感到失望,而这正是有意为之。忠实于原代码的移植版本应当与其来源代码保持一致。

这会产生明显的权衡。Zig能够简洁地表达某些低级操作,而Rust则倾向于在类型、生命周期、所有权以及不安全代码的边界等方面更加明确。因此,一个简洁的Zig函数在Rust中可能会变得明显更长。这种长度的增加是由于追求行为等价而非简洁性所导致的,并不意味着结果有误。

将两种观点区分开来会有帮助:

  • “这不是我从头开始编写的Rust代码。”
  • “这段Rust代码是错误的。”

前者属于风格层面的判断,后者则是缺陷。将二者混为一谈会导致对机械性代码转换的错误批评。

先实现功能,再优化代码

所采用的方案将机械式翻译与后续优化视为两个独立的阶段:首先让Rust的行为类似于Bun,然后再对Rust本身进行改进。通过这种方式划分工作,团队无需同时处理两个相互关联的庞大变更。如果在清理过程中出现退化现象,可以将其归因于清理操作本身,因为基准版本早已通过相同的测试得到验证。

正确理解unsafe代码块

那些浏览过该移植版本的观察者注意到其中存在大量unsafe代码块,便认为这是Rust质量低下的表现。这种反应虽可理解,但却忽视了这些代码块产生的原因。

Rust要求那些可能具有危险性的操作,比如解引用原始指针或调用外部函数,必须用unsafe明确标记。Zig没有类似的标记机制。Bun的原始代码充斥着低级内存操作、指针处理以及对C和C++组件的调用,因此在进行忠实翻译时,自然会有大量内容以Rust的unsafe形式呈现。此外,Bun还嵌入了JavaScriptCore并与其他原生库交互,而Rust代码与C或C++代码之间的边界本质上就需要使用不安全的操作。

换言之,这次移植并没有增加新的危险,只是让原有的危险变得可见。仅统计unsafe代码块的数量并不能反映代码质量,更有意义的问题是:

  • 为什么这项特定操作必须使用不安全机制?
  • 该不安全区域是否小到足以被合理控制?
  • 其周围的安全代码能否防止调用者误用它?
  • 它的行为是否有测试覆盖?
  • 在后续处理中能否缩小或移除它?
  • 这些问题也为清理阶段提供了清晰的路线图:缩小不安全区域,将其封装在安全的抽象层中,并记录每个抽象所依赖的不变条件。

    翻译如何发现改进点

    直接移植仍可能揭示原始设计中隐藏的机会。有案例显示,Zig实现通过C++来获取某些信息,而在Rust中则可以直接访问相关的线程本地数据,无需经过那次中间调用。

    结论并非是 Rust 在速度上优于 Zig;这样的概括经不起仔细推敲。实际意义更为具体且有用:当代码需要在不同语言之间迁移时,就必须有人或某种机制仔细审查每一条调用路径,而这样的审查有时会暴露出本不必要的工作。这类发现应当逐个案例进行评估,而非被卷入语言之争。

    工程工作的转移方向

    迁移并没有消除工程工作,只是将其位置改变了。在传统开发中,大量时间都用于编写实现代码;而在智能体辅助开发模式下,更多时间则用于:

    • 定义约束条件与转换规则
    • 设计智能体运行的工作流程
    • 编写并完善测试用例
    • 审查生成的代码变更
    • 诊断故障原因
  • 决定代理程序可以修改哪些文件和系统
  • 在每轮处理后优化反馈机制
  • 工程师负责的是生成和检查代码的整个系统,而非代码本身的每一行。对于大型项目而言,这一流程大致如下:需求定义、代码生成、编译、测试、审查、修正,然后循环往复。该模型会参与其中的多个步骤,但流程的设计决定了最终输出的价值。真正的创新在于这种工作流程,而非代码行数。

    为何 Bun 是理想选择

    并非所有代码库都适合采用这种方法,而 Bun 具有以下几大优势:

    • 有可用的实现版本作为参考
    • 架构稳定且已知
    • 可通过现有测试套件观察到的行为
    • 明确选定的目标语言
    • 能够拆分为相对独立文件和组件的代码库
    • 一个单一且可衡量的目标:保留程序,仅更换语言

    这与要求智能体设计新产品的情况不同,后者没有参考实现,“正确”标准也因人而异。一般而言,所需行为描述得越精确,实现的自动化程度就越高。但这并不意味着智能体可以放心承接任何大型项目,只是说明当目标可衡量时,自动化才能发挥最佳作用。

    迁移无法证明什么

    这类结果很容易被过度解读,因此有必要明确其局限性。Bun 的示例并不能证明人工智能可以取代软件工程师,也无法说明所有代码库都能以这种方式转换,生成的代码就一定易于维护,或是 Rust 总比 Zig 更快。更不用说它能为将大量人工智能生成的代码合并到普通产品代码中提供依据。

    事实上,它展现的恰恰是相反的情况。这项工作依赖于严格的测试、明确的规则、验证、审查、调试以及持续的人工监督。对已公开工作流程的分析也表明,这些智能体并非被放任运行;人们会监控整个过程,并在出现问题时进行调整。工程判断的重要性并未降低,反而成了所有其他工作的基础。

    验证正逐渐成为瓶颈

    几十年来,编写代码一直被视为效率较低的环节。假设某个智能体能在开发者原本需要500行代码的时间里生成10,000行,人们首先会为这种速度提升而欢呼,但紧接着就会出现另一个问题:谁来检查这10,000行代码呢?

    如今生成代码的成本已经很低,但确保其正确性的成本依然高昂。在运行时环境、编译器、数据库、操作系统以及那些稍有错误就会导致严重后果的安全敏感代码领域,这一成本更是急剧上升。这表明,那些负责验证而非生成的工具——如测试套件、可观测性工具、静态分析工具、模糊测试工具、形式化方法、沙箱环境以及对抗性审查机制——正变得越来越重要。从编码智能体中获益最多的,或许反而是那些拥有最完善验证系统的团队,而非那些能使用最佳模型的团队。

    可用于自身 AI 辅助改动的实践方法

    应用这些方法并不需要规模庞大的项目。无论是将服务在不同框架间迁移,还是将 JavaScript 代码库转换为 TypeScript,它们都能发挥同样作用。

    在重大变更前先建立测试机制

    完善的测试套件即为一份可执行的规范。能够自动检测的行为越多,就能越安全地将实现工作交由他人处理。如果测试覆盖率较低,提升覆盖率应是迁移过程中的首要任务,而非事后才考虑的事。

    把规则明确写下来

    避免使用“重写这个”之类的模糊指令。应明确规定架构、命名规则、允许与禁止使用的依赖项、不可使用的快捷方式、错误处理要求以及每项工作的验收标准。

    将生成与审核分开进行

    智能体绝不应成为自身输出内容的唯一评判者。通过独立的审查环节——无论是人工审核、具有不同任务的另一个智能体审核,还是两者结合——能够带来新的视角,发现生成环节所忽视的问题。

    在少量具有代表性的样本上进行测试

    首先在少数典型文件上运行该流程。当错误假设仅影响十份文件时,修正成本很低;而一旦在上万份文件中普遍存在,则修复成本极高。

    在未被证实之前将生成的代码视为不可信

    能够通过编译的代码并不等同于能正常运行的代码。编译只是众多检验环节中的一个,并不能作为代码正确的证明。

    让相关人员对关键决策负责

    智能体能够极快地探索大量可能的实现方案,但决定系统应执行什么操作以及哪些风险是可以接受的,仍属于人类的职责范围。

    超越 Zig 与 Rust 的争论

    人们很容易将这个问题框定为 Zig 对抗 Rust、手写代码对抗人工智能,或是人类对抗智能体。但这些视角都未能给出深入的解释。Bun 的移植其实更应被视作一种实验,旨在探讨当编写代码的成本几乎为零时软件应如何构建。一旦某个系统能够处理数十万行代码,核心问题就从“我们能实现它吗?”转变为“我们能否建立一套可靠的流程来判断实现结果是否正确?”。这其实是两种截然不同的工程问题。

    应如何评判这一成果

    最初的翻译仅是第一步。经过机械式移植的代码库可以随着时间逐步变得更符合习惯用法:对不安全区域进行审计并减少,优化抽象层,调整性能,并逐步废弃兼容层。与任何生产系统一样,最终还是要看它能否为真实用户所用。相关资料指出,在迁移之后Bun仍持续发布新版本;可查看该项目的代码仓库和变更日志以了解其当前状况。

    有意义的评估应来自长期表现,而非重写规模:

    • 各版本间的运行时是否保持稳定
    • 代码是否变得更易于维护
    • 性能是否仍具有竞争力
    • 与内存相关的错误是否变得较少
    • 贡献者是否能够理解并改进新代码

    核心要点

    • Bun 迁移的价值在于规则、测试、审查与修正的循环过程,而非生成的代码量。
    • 将重写视为保持行为一致性的移植工作,能为自动化工具提供可衡量的目标,也为审查人员提供具体的检查清单。
    • 移植指南能够缩小可接受输出的范围,从而实现大规模审查。
    • 在忠实移植过程中,使用冗长或大量依赖 unsafe 机制的 Rust 代码往往是实现等价性的必然代价;应依据其使用边界而非数量来评判 unsafe 代码。
    • 随着代码生成成本的降低,团队间的差异将体现在验证工作上,而设计相应的反馈循环正逐渐成为工程工作的核心。