大语言模型时代下的编程语言与类型安全

HN Build in Public AI 2026-09-06T12:10:13.450273

我不得不承认,智能体编程(agentive coding)已经是大势所趋,尽管我仍然觉得它有点被高估了。不过,Elon Musk 比我看得更乐观。他认为有了大语言模型(LLM),编译器将变得多余,甚至还放出过这样的言论:

也许到今年年底,事情就会发展到你根本不需要费心去写代码的程度。
AI 直接就能生成二进制。

他还说:

代码本身会消失,取而代之的是直接生成二进制。再下一步,就是神经网络直接实时生成像素。

关于这一点,Elon Musk 错了。在大语言模型时代,我们仍然需要编程语言,也需要为它们配备好的编译器。就像人类借助这些工具能把活干得更好、更高效一样,LLM 作为以人类为蓝本打造的推理系统,也是如此。没有理由相信编程语言的价值会因此降低。

实际上,我觉得 Musk 不只是错了,而且是大错特错。问题不在于编程语言在大语言模型时代会变得没那么有用、没那么重要,甚至也不是说它们的重要性和以前持平——编程语言只会变得更加重要、更加不可或缺。

对一位熟练的程序员来说,编程语言本身的质量反而不是那么关键。一门编程语言越好,能替程序员省掉的事情就越多,但熟练的程序员就算语言差一些,也照样能扛下来。虽然编程语言质量越低,最终产出质量也会跟着下降,但下降的速度相对缓慢。

对 LLM 来说,编程语言的质量就重要得多了。我们能替 LLM 省掉越多杂事,它就能把有限的上下文越集中地用在真正相关的问题上,而不是浪费在无关紧要的细节里;我们给它的约束越严格,得到的产出质量就越高。LLM 本身就很难把所有相关情况都塞进有限的上下文里——它们经常顾此失彼,最后生成一堆垃圾。所以,它们不仅需要编程语言和编译器,需要好的编程语言和好的编译器,而且比我们人类更需要这些东西。

为什么 LLM 不会直接输出机器码

写机器码——甚至汇编语言——本身就很难。这项工作要求对细节极度专注,还布满了各种反直觉的坑(姑且称之为 footgun),在性能和正确性上都容易让人栽跟头。如果给工作排一条光谱,一头是偏感性、靠直觉的创意型工作,另一头是数学化、严谨缜密的逻辑工程型工作,那么写机器码妥妥地落在工程这一端。

对人类来说,写机器码太难了。想写得高效,人其实得先多做一步:把计划单独写出来——本质上就是先写一份高级程序。否则要同时掌控的复杂度实在太多,不做周密规划,任何人的工作记忆都会被瞬间压垮。

等人类把计划写完——注意,这份计划的详细程度已经不亚于一份 Rust 程序——他们还得手工把计划逐条翻译成机器码。而这一步,只要人类直接改用 Rust 来写,编译器很快就能完成,比人快得多。编译器生成机器码不光是速度快,质量也更高:性能更好,引入隐蔽 bug 的概率也小得多。

这里引用一段我早先对 LLM 更宏观的探讨:

Rust 编译器具备的能力,我不具备。我无法 100% 手工验证一个 Rust 程序是否正确。给我足够的时间,我也许能把 Rust 程序手动转成等价的汇编或机器码——但过程容易出错,做出来的版本性能也更差,还得反复尝试很多次才能跑通。

凡是我比不过 Rust 编译器的地方,LLM 也一样比不过,甚至差得更远。

LLM 比人类更需要明确的结构化规划和严谨性。即便目标不是手写机器码,先让 LLM 写出一份高层级的抽象计划、再把它交给一个拥有全新上下文的新实例去执行,效果也会更好。多智能体(multi-agent)系统眼下正火,正是这个原因:一个多阶段流程的不同环节,被拆到不同的上下文、不同的 LLM 实例里去处理,有的管高层设计,有的管底层实现。

多智能体大语言模型工作流的核心洞察在于:分工是关键。不同的任务需要不同的上下文和指令,不能被其他智能体干扰。这些不同的子任务,可能需要不同的模型,也可能需要不同程度的推理投入。

而我还要补充一点:并非所有组件都应该用 LLM 来实现。在最底层的那一步——把最低级的计划转换成真正的机器码时,LLM 的表现和人类一样糟糕。首先,它们会犯粗心的错误,比如漏掉指针失效的问题(在 C++ 里就很痛苦,裸汇编里更甚)、复制错偏移量等等。这类精确而繁琐的工作,恰恰是计算机最初被设计出来要干的事——遵循明确的固定算法,不需要任何创造力和直觉,每次都能得到最优解。

LLM 是计算机进行直觉推理的方式。而要做到数学上的严谨,由传统程序(比如编译器)打造的传统工具更合适。LLM 之所以能可靠地做算术,也只是因为我们在内部给了它一个计算器工具而已。同理,在生成机器码这件事上,它们也需要内部工具来辅助。

理想状态下,应该是「直觉/模糊推理」组件(如 LLM)和严谨的数学工程型组件(如编程语言)混搭使用。随着这类混合推理系统越建越多,我们会得到越来越好的结果——前提是我们能构建出有效的编程语言。

顺着这个思路,我们还可以做一些「接线装置」,把这些新编程语言更顺畅地接入 LLM,比如更好地集成 LSP(语言服务协议)之类的工具、为内部笔记提供草稿空间组件、为公式化数学推理和视觉推理开发插件,等等。但就眼下而言,我最想强调的一点是:LLM 需要编程语言和编译器。

LLM 需要什么样的编程语言?

所以说,LLM 离不开编程语言。但它们需要的是哪种编程语言呢?

要弄清楚这个问题,不妨退一步想:LLM 在什么情况下表现最好?顺着这个思路捋下去就会发现,基于 LLM 的编程智能体在拿到某种衡量自身表现的指标时,效果是最佳的。大量的测试用例有帮助。验证机制也很有帮助。(当然,人类也一样,但 LLM 对上下文的理解本来就更浅一层,所以它们比人类更依赖指标和验证。)

听起来是不是很像静态类型?像 Rust 那样?

像 Rust 这样的编程语言,想用错都难。Rust 的一个核心设计目标就是“能编译,就能跑”(If it compiles, it works)。靠着借用检查器(borrow checker)加上严谨又不失灵活的类型系统,Rust 把这句话落到了许多人不看好的程度——网上满是这类成功案例,随便找个 Rust 程序员,他都能兴致勃勃地给你讲上一堆。Rust 编译器内置的规则多到离谱(换作其他语言,这些大概都是需要手动开启的 lint),效果上就相当于自带了一套测试用例。

所以,Rust 和基于 LLM 的编程,可以说是天作之合。你可能会说,LLM 写 Python 和 JavaScript 的水平比写 Rust 高多了——但这根本不是一回事,那只能说明 Python 和 JavaScript 在训练数据里出现得更频繁罢了。在 Rust 上,我亲眼见过 LLM 看到编译器报错后当场调整代码的样子。我都不敢想象 LLM 去写 C++ 会是什么光景——内存破坏、未定义行为满天飞。如果有谁真的在用智能体工作流写 C++,拜托告诉我一声你的体验如何。

LLM 写出来的代码,跟人写的相比就是“及格线水平”,这是每个有判断力的程序员在实践里都能得出的结论。但 Rust 代码再“潦草”,也有个下限。Rust 写的烂代码,再烂也烂不过 C++ 的烂代码。

LLM 时代下的编程语言与类型安全

在我看来,Rust 在 LLM 环境中的优势还远未被充分挖掘。理想情况下,智能代理(agentic)系统应该更多地借助 LSP(语言服务器协议)这类工具来工作。当代理直接把改动输出为 Rust 代码时,它没必要再主动去查询一遍编译器诊断信息。它每写一次代码,就应该立刻直面报错、拿到修复机会——或许可以在父上下文的一个分支里进行,同时把相关函数的签名和文档也一并注入进去。像 Rust 这样的静态语言,让这一切都变得简单得多。

不过,Rust 覆盖的只是软件工程版图的一部分。它专注于系统编程领域,比如数据库、数据管道、底层网络、语言运行时和操作系统内核。而像图形用户界面(GUI)、数据科学这类大多数程序员真正在做的开发,Rust 反而有点力不从心。

Rust 对类型安全的高标准,跟它对系统编程的专注其实是两回事。Haskell 就是一个例子——它同样追求而且确实实现了「能编译就能跑」的目标,并且自带垃圾回收,在 GUI 场景下也能表现出色。只可惜 Haskell 对「实际商业成功」这件事始终不太上心。Rust 把 Haskell 这类研究型语言的部分能力带给了更广的受众,但覆盖面仍只限于某些领域。我们需要在更多领域拥有 Rust 式的编程语言。

未来

编程语言设计的工作,在 LLM 时代远未结束,反而才刚刚开始。我们需要在更多编程语言中注入 Rust 那样的严谨性;我们还需要利用这种严谨性的智能代理工具链,把严谨性融入我们构建这些多代理模型中 LLM 组件的方式。

总的来说,我们要做的是批判性地思考、聪明地构建,而不是像某些与技术世界脱节的商业大亨那样,遇到什么问题都随手堆上更多 LLM,然后想当然地以为这样就够了。

订阅

评论

查看原文