DHH,Ruby on Rails 的创造者,让 AI 把 Campfire Once 从 Rails 重写成 Rust。

按原文说法,他自己不想读,也不想写 Rust,于是让机器代劳。之后又有了 Elixir 和 Go 版本。

听上去像一条 AI 编程的完美广告:大神动嘴,模型动手,多语言重写一键完成。

但开发者 Piotr Sarnacki 拆开一看,发现这事没那么爽。

Rust 版在高压测试里,新消息通知送达率一度只有 1%。

更刺激的是,把配置从 256 改到 16384,送达率又能从约 14% 拉到约 90%。

这不是爽文吗?

作者却泼了盆冷水:这个版本可能更糟。因为事件送达延迟从 11 秒飙到 130 秒以上。系统没有快速失败,反而把用户拖进漫长等待。

一句话:AI 能替你写代码,但不能替你决定系统该怎么失败。

你的 prompt 太模糊,AI 就替你抛硬币

老实讲,这个实验最有意思的地方,不在 Rust,也不在 Elixir。

它像一个显微镜,照出了很多人用 AI agent 时的真实状态:需求只写一半,细节全靠模型脑补。

原文提到一个很狠的判断:

if your prompt is not specific enough, many decisions are a coin flip.

翻译过来就是:如果你的提示不够具体,很多决定就是抛硬币。

软件工程里,大量问题根本没有唯一答案。

要不要保持向后兼容?

更在意延迟,还是吞吐?

高负载下能吃多少内存?

崩溃之后,丢掉新内容通知能不能接受?

你可能觉得这些都不重要。可只要你不写清楚,LLM 就会在实现时悄悄替你回答。

Rust 版就是这么干的。

它没有保持 100% 向后兼容,比如去掉了 CSRF token,让缓存更容易。它还放弃了 Redis,改用进程内队列处理通知。

Elixir 版则更接近 Rails 原版。

你看,表面上是“Rust 重写”和“Elixir 重写”的对比。实际上,很多差异根本不是语言造成的。

AI 听到的是“用某种语言重写”。但真正影响结果的,是那些没被说出口的约束。

这点我挺认同。很多团队以为 AI 差在智商,其实先差在需求。项目经理给一句模糊口号,工程师都会跑偏,何况模型。

不读代码,坑不会自动消失

接下来更尴尬。

有人发现,Elixir 版里出现了单进程顺序处理所有 SQL 查询。连读请求也不能并发。这个抱怨很合理。

DHH 似乎觉得,这会让 Elixir 看起来不太体面:AI 没能写出高性能代码。

但作者转头看了 Rust 版,发现也不轻松。

Rust 版里,一些数据库操作并不是 async。

问题在于,Rust 的 async runtime 采用协作式调度。一个任务不让出线程,同一个 worker 上的其他任务就只能干等。

如果一条 SQL 查询要等 100ms,你还用阻塞方式卡在那里,其他任务就会陪跑。

锁也一样。

async 环境里,更稳妥的做法是使用 async 锁。若用同步锁,还拿在手里 10ms,同线程上的任务都会等它,而它自己并没干活。

所以作者说得很直白:编程没有被“解决”,你仍然需要知道自己在做什么。

这里有个细节值得咂摸。

DHH 是顶级工程师。原系统也不是凭空冒出来的玩具。评论区甚至有人说,这已经是 LLM 的理想场景,因为 90% 的思考早已由原系统开发者完成。

连这种局都能暴露问题,普通项目凭什么觉得可以闭眼交付?

说实话,AI 可以帮你跳过敲代码的环节,但跳不过系统运行时的报复。

服务器不会因为你没读代码,就对你温柔一点。

1% 送达率,打脸的可能不是 Rust

基准测试这段,是全文最像连续剧的部分。

最初,Zach Daniels 测了高负载下的新帖通知送达率。结果发现,Rust 版在重压下只有 1% 成功送达。

这数字太难看了。

Elixir 版呢?

在约 1.7k 通知里做到 100% 送达。Rust 版面对的是 6-7k 通知,只送出 1%。

于是,Elixir 社区有人迅速得出结论:Elixir 并发更强,Rust 协作调度不行。

但作者提醒,先别急着开香槟。

DHH 和 Zach 的测试都是 closed-loop。

简单说,就是 N 个客户端收到响应后,立刻发下一条。它测的是系统单位时间能吞多少请求。

现实世界未必长这样。

真实压力常常来自大量用户同时行动,不会等前一个人完成。

所以作者改用恒定到达率重测。

100 POST/s 下,Rust 客户端收到约 14% 事件,Elixir 约 60%。

看起来 Elixir 还是赢。

可再看另一面:这个流量下,Rust 没有 HTTP 错误。Elixir 约 23% 的 HTTP POST 请求超时。

最糟的事件送达延迟接近 180 秒。

这就有意思了。

Rust 在事件滞后时,会断开客户端。浏览器重连后会拉取最新消息,丢失事件的影响被大幅削弱。

Elixir 则可能让用户等一条新消息等上几分钟。

哪个体验更好?

是后台悄悄重连,还是盯着屏幕等 3 分钟?

单一指标真的会骗人。

送达率很重要,但错误率、延迟、内存、用户感知,也都在系统里投票。

一行配置救回 90%,但作者说这更糟

真正的反转在这里。

Rust 版为什么丢消息?

它用了 tokio::sync::broadcast 通道,把事件广播给多个连接。这个通道有容量上限。接收端处理不过来,就会出现 RecvError::Lagged。

重写版把容量设为 256。

Elixir 那边,GenServer 负责事件投递,默认邮箱没有上限。

于是作者改了一行:

- stream_capacity: 256
+ stream_capacity: 16384

在 100 req/s 下,事件送达率从约 14% 提到约 90%。

如果只看这张表,Rust 似乎反超了。

但作者说,这个版本反而更差。

原因是,它把“快速失败”变成了“慢慢积压”。

容量拉大后,pMAX 事件送达延迟从 11 秒升到 130 秒以上,行为开始接近 Elixir。

个人觉得,这里点破了工程里一个很残酷的真相:能送达,不等于值得等。

对实时聊天来说,11 秒已经太长。用户要的是新消息,不是考古。

如果接收方来不及消化事件,让它断开重连,重新拉取最新消息,可能比硬撑着排队更合理。

这就是失败设计。

更妙的是,Elixir 也不是天然免疫。

作者提到,在 100 req/s 压测中,Elixir 版内存跑到 1.8GB。

问题就变成了:你宁愿丢一些消息,还是宁愿被 OOM 杀掉?

没有魔法。

Elixir 很擅长并发,写并发通常更容易,但它也要付出内存、速度和控制力的代价。rustler 的存在,本身就说明问题。

任何语言都要面对失败模式和边界条件。

你把边界留白,系统就会自己找一个边界。通常是更贵的那种。

AI 没有解决编程,它只是把思考外包的账单提前寄来

这件事最容易引发争吵,因为每个人都想站队。

Rust 粉可以说,一行配置就能改善。

Elixir 粉可以说,默认行为更稳。

AI 粉可以说,这只是早期问题。

但作者真正想说的,不在语言优劣。

他反复强调几件事:你仍要批判性思考;你要知道自己在做什么;不要基于单一指标下结论;想做可靠系统,必须知道何时失败;基准测试很难。

这些句子放在 AI 时代,突然变得有点刺耳。

过去,代码写错了,至少是人在写。

现在,很多人希望 AI 生成、AI 翻译、AI 优化,最后自己只负责点确认。

评论区有条观点很到位:如果你给团队不完整需求,结果可能也差不多。

AI 只是把这个问题放大了。

它更快,更自信,也更不会主动追问你:这个系统到底能不能丢消息?

老实讲,我并不反对 AI 写代码。它确实能省下大量机械劳动。

但这个案例提醒的是另一种风险:我们把“生成代码”误认成“完成系统”。

代码只是表面。

系统包括兼容策略、队列边界、背压、超时、重连、内存、失败语义,以及用户到底能忍多久。

这些东西,prompt 里没写,模型就会自己补。

补得看起来像那么回事,跑起来才知道代价。

所以,真正被这次实验戳破的,是“不用读代码也没关系”的幻觉。

如果连读代码都嫌麻烦,谁来确认系统什么时候该失败,用户什么时候该重连,内存什么时候该报警?

【锐评】:AI 能替你敲代码,但替不了你决定系统什么时候该体面地崩。

参考链接:
https://itsallaboutthebit.com/i-am-sorry-but-you-still-have-to-think/