Ohhnews

分类导航

$ cd ..
Jetbrains Blog原文

OpenAI工程师分享:如何用AI提升Rust开发质量

#ai#rust#软件开发#代码审查#openai

“有些人用 AI 写更多代码。我用 AI 让我的代码更正确。”Predrag Gruevski 的置顶帖概括了他用 AI 做 Rust 开发的方式。Predrag 在 OpenAI 从事前沿系统的工作,他创建了 cargo-semver-checks 和 Trustfall。在他的工作中,几乎不允许出错。尽管如此,他的大部分工程工作仍然把 AI 放在流程中,而且他的质量标准还提高了。但这是怎么做到的?

[LOADING...]

我们想知道这在实践中是什么样子,于是邀请他参加了一场 RustRover 直播。他的演讲是我们与 Rust 基金会合作的 AI & Rust 直播系列的一部分,该系列关注 Rust 与 AI 交汇处的真实用例、成功经验和失败教训。本文聚焦于 Predrag 最实用的建议:该审查什么、如何给 AI 编码智能体写提示、如何验证它们的产出,以及当它们出错时该怎么办。

你可以在这里观看完整直播录像 👇

使用 AI 时,哪些 Rust 代码最值得关注?

在把任何工作交给 AI 智能体之前,先按“做错要付出多大代价”给工作排序。并非每一行代码都同等重要。有些代码行值得你投入比别人多 1000 倍的注意力。当你亲手敲下每一行时,写 10 行 unsafe Rust 和写 10 行普通代码花的时间差不多。现在 AI 接手了普通代码,你就可以把这部分时间挪到那些出错的代价很高的代码行上。

把你的代码想象成一条风险刻度。一端是错误很容易被发现和修复;另一端是错误可能潜伏数月,并造成真正的损害。下面列出这条刻度上的三个点:

低风险代码。 一个 GitHub Actions 工作流就是很好的例子。你知道它该做什么,智能体也读过文档,那就让它写这个工作流并测试它。如果一个 CI bash 脚本坏了,而且不涉及任何安全问题,就让智能体去修。

其他人依赖的逻辑。 cargo-semver-checks 能在破坏性变更发布之前将其捕获,它现在运行在 Rust 自己的 CI 中。在它被引入之前的五年里,它本可以捕获 Rust 发布中大约六起被迫回移植的意外破坏性变更。反过来,如果它的逻辑错了,告诉某人他们的改动是破坏性的、但实际上并不是,那就是非常严重的问题。所以在为 Rust 的 CI 做准备时,Predrag 让 AI 构造一些会触发误报的标准库新增内容。这种严格审查发现了同样可能影响普通库的误报。这类代码值得你投入多得多的精力。

Unsafe 代码。 这里的错误可能意味着内存不安全、安全问题,或者一个十亿分之一概率才出现一次的海森bug。听起来很罕见,直到你在数据中心里运行它。在那里,0.0000001% 的概率损坏数据意味着它一直在发生。在一个真实案例中,这类问题仅仅由 unsafe 块中的 20 行代码造成。这才是值得逐行阅读、甚至手写的代码。为 100 行 unsafe 代码花上四个小时是合理的代价。

如何提示 AI 编码智能体写出可靠的 Rust?

描述你想要什么、你希望它怎么完成,以及“足够好”是什么意思。不要满足于“请写这十行代码”。一个任务可能消耗数十亿 token,产出的代码远远超出你能读的量,所以工程工作必须发生在提示里。要写出好的提示,你需要做三件事:

勾勒设计

向智能体介绍任务,就像你向一位技术非常娴熟、非常认真但缺少你背景信息的同事交代工作。在白板前,你会画一些方框和箭头。你会解释类型如何工作、需要哪些函数和字段、数据流向哪里。这些是承重的部分。把它们写进提示里,让智能体在此基础上扩展。

测试也一样。测试用例内部的变量名远不如测试结构重要。测试是否覆盖了所有主要情况?正确的事情是否在正确的时间点发生?把这个结构描述在提示里。

说明哪些假设是可以接受的

如果你告诉模型要彻底、要测试所有情况,你会得到针对停电期间陨石撞击的测试。对于航空软件,这种谨慎程度是对的。对大多数代码来说,这是白费力气。告诉智能体哪些假设是可以的,并要求它检查某个场景是否真的可能发生。

保持精确

Rust 的问题往往有带条件的答案。问某个改动是否是破坏性的,准确的回答可能是“是,但有六个附加条件”。这取决于该 trait 是否已经存在、它是否在已存在的类型上实现、该 trait 是否被 sealed,等等。你的提示也需要同样精确地说明你想要什么、不想要什么。

所有这些都要花时间,而且会有回报。为了在 Rust 编译器和标准库中寻找提速机会,Predrag 花了 45 分钟为 Codex 写了一条提示。它告诉智能体去哪里找:那些很久没人优化过的热点代码。它说明了优先选择哪类改动:复用项目中其他地方已经奏效的想法的小改动。它还说明了如何做基准测试:交错运行,几何平均值至少提升 1%。

在一个 PR 中,Predrag 删掉了新 trait 求解器中的一行代码。这行代码重新计算了代码在 40 行之前已经算过的东西。删掉它让新 trait 求解器快了多达 7.5%。一旦有人指出来,所有人都会同意这行该删,但要找到它需要大量工程工作和思考。

“人们很容易把 AI 想成一匹更快的马。这个例子说明它并不是一匹更快的马。原则上,我们本可以手工完成所有这些工作。只是要花五年才能找到这些优化。” [LOADING...] Predrag Gruevski OpenAI 前沿系统软件工程师

如何验证 AI 生成的 Rust 代码?

把审查重点放在设计和代码测试方式上。一旦智能体能够可靠地遵循指令,逐行阅读就不再能为 Predrag 发现问题了。于是他转向更高层次的问题:API 设计得好吗?我们是否以所有正确的方式测试它?

为什么高级测试突然变得可行

代码覆盖率、分支覆盖率、变异测试和确定性模拟一直都有,但常规使用它们的成本太高。一个能可靠遵循流程的智能体改变了这一点。把流程写进提示,智能体就会做繁重的工作。

一套可以交给智能体的验证流程

假设你在构建一个内部包含状态机的组件。每一步都应该能捕捉到前一步可能漏掉的问题: [LOADING...]

  1. 测试结构。 一个测试每个状态和每次状态转换的 harness 能证明每一个都行为正确。
  2. 检查关键逻辑的覆盖率。 覆盖率工具能显示测试执行了哪些代码。确保所有非平凡的逻辑都被包含在内。
  3. 检查测试能否捕获 bug。 仅仅执行到某一行,并不能证明测试检查了它做了什么。cargo-mutants 会故意向你的代码中注入 bug,并检查是否有测试能捕获它们。每一个存活的变异体都在提示你去调查:测试是否漏掉了某个重要行为。不断添加测试用例,直到代码在所有关键之处都被钉死。
  4. 在 Miri 和 sanitizer 下运行 unsafe 代码。 让智能体推演重要的情况,并在 Miri 和 ASan 之类的 sanitizer 下测试它们。这些工具与对安全不变量的仔细审查配合使用,而后者仍然值得你全神贯注。

作为提示,这套流程可以写成这样:

这个组件内部有一个状态机。状态是 [...],转换是 [...]。写一个 harness 测试每个状态和每次转换,并检查是否发生了正确的事情。
运行覆盖率工具,确保测试执行了所有非平凡逻辑。针对非法输入的 .unwrap() panic 分支不需要测试。运行 cargo-mutants。对于每个存活的变异体,检查测试是否漏掉了某个重要行为,如果漏掉了,就添加测试用例。
对于任何 unsafe 代码,推演重要的情况,为它们写测试,并在 Miri 和 ASan 下运行这些测试。安全不变量我会自己审查。

当 AI 智能体出错时该怎么做?

或早或晚,智能体会交给你一个你不喜欢的结果。用同理心去处理这个问题。弄清楚智能体为什么选择了它采用的方式,以及它需要知道什么才能做出不同的决定。如果你看不出来,就问它。

然后把答案写进你的配置里。如果智能体总是测试不切实际的故障,比如那次陨石撞击,就在你的智能体指令文件(例如 AGENTS.md)里加一行。告诉它对现实世界的故障做出务实的假设。你也可以更新个性化提示,或调整你的技能配置。下一次,智能体应该能做出更好的判断。

没有人天生就擅长使用 AI 工具。变得擅长可能要消耗数十亿 token,而我们都还在学习。

如何判断 AI 生成的代码是否足够好、可以发布?

这取决于你在构建什么。对于原型开发这类任务,vibe coding 完全合理——你只是想知道一个方案到底行不行;在你自己用的小应用里也没问题。如果那个晚上 7 点给你开灯的东西坏了,那就坏了吧。这种场景下 vibe coding 没问题。

生产代码需要更高的标准。用 AI 去达到你独自达不到的水平。

要做到这一点,就倒推着来。想一想你会如何说服别人相信你构建的东西是好的,哪怕对方不了解你的领域。对于 Rust 编译器上的工作,证据意味着几行代码、更快的基准测试、通过的测试套件,以及任何人都能读懂和理解的改动。一旦你知道“证据”长什么样,就把你的工作流搭建成能产出它的样子。很快你就会意识到,你要说服的那个人可能就是你自己。

你推送的每一段代码都带着你的签名。它确实是从你的 Git 客户端发出来的。用 AI 确保这是你愿意公之于世的代码。

还想了解更多?观看上方的完整对话。关于 Rust 与 AI 的另一面,请阅读我们关于用 Rig 构建 LLM 应用的文章。

AI 对 Rust 开发有用吗?

有用,尤其是当你用它来提升质量标准、而不只是写更多代码时。智能体让那些过去太费力而难以运行的技术变得可行,比如变异测试、完整的状态机覆盖,以及在 Miri 下检查 unsafe 代码。它们还能翻查 Git 历史,找出很久没人优化过的热点代码。

你需要逐行阅读 AI 生成的 Rust 代码吗?

不需要。让审查强度与风险相匹配。对于 CI 脚本这类低风险代码,测试它能工作通常就够了。其他人依赖的逻辑值得你投入多得多的时间,而 unsafe Rust 则值得逐行阅读或手工编写。

AI 编码智能体能写 unsafe Rust 吗?

能,但 unsafe Rust 需要你处理的所有代码中最多的谨慎。让智能体推演重要的情况,并在 Miri 和 ASan 之类的 sanitizer 下测试它们,同时你自己审查安全不变量。