为什么 TypeScript 7.0 用 Go 重写

微软将TypeScript编译器移植到Go,实现了10倍性能提升,并深入探讨了Go在智能体开发时代的优势及其对技术栈的影响。

核心内容:

  • TypeScript 7.0 编译器用 Go 重写后,构建速度提升了一个数量级,这得益于 Go 的简单函数、垃圾回收和共享内存并发,使得一对一的代码移植成为可能。
  • 在智能体开发循环中,Go 的快速编译、确定性依赖管理、编译时错误捕获和生态稳定性显著减少了迭代成本,保护了稀缺的上下文窗口资源。
  • Go 并非要取代 Python 或 TypeScript,而是作为智能体基础设施层的默认选择,与 Python 生态互补,通过工具实现无缝集成,优化整个软件开发生命周期。

过去一年里,TypeScript 背后的团队将 TypeScript 编译器及工具链移植到了 Go。不是 Rust,不是 C++,而是 Go。微软的数据显示,基于 Go 的 TypeScript 7.0 构建速度提升了一个数量级。

在 AI 辅助开发的时代,全球最大的 JavaScript/TypeScript 组织之一,为其旗舰工具选择了 Go。

微软的理由非常务实,Anders Hejlsberg 对此做了详细阐述:现有编译器以函数为主体的代码风格几乎可以一对一地移植到 Go;新旧编译器都依赖垃圾回收;而原生代码配合共享内存并发,正是那 10 倍性能提升的来源。公告中一个字都没提 AI。但让这种务实的一对一移植成为可能的那些特性——简单的函数、没有隐藏魔法、有 GC、代码量小到足以让一个团队完全掌握——恰恰是下一代开发模式所要求的品质,而这正是本文要讨论的核心。

Go 的诞生基于一个赌注:为读者优化,而非为写者优化,以此来换取更少的代码被误读。大语言模型读代码的量远超任何人类,而随着它们写出越来越多的代码,人类变得更加偏重"读"的角色。智能体(Agentic)开发,是对 Go 设计初衷最极致的检验:可读性、可维护性、大规模下的长期正确性。

Python 和 TypeScript 呢?

表面上看,在 AI 时代为 Python 和 TypeScript 站台的理由相当有力。Python 拥有 PyTorch、LangChain 以及整个机器学习生态。TypeScript 拥有 Web 和庞大的开发者池。大语言模型在海量的 Python 和 TypeScript 代码上训练,写起它们来流畅、自信、迅速。

Python 和 JavaScript 的设计初衷是脚本语言:写起来快,宽容,动态。TypeScript 在此基础上加了结构化,但它只是编译期的覆盖层——类型在运行时会擦除,所以 TypeScript 的任何类型保障在运行时都不复存在。TypeScript 没有取代 JavaScript,而更像是与它并行生长:GitHub 2025 年 Octoverse 报告显示 TypeScript 在月度贡献者数量上超过了 Python 和 JavaScript,但 JavaScript 在同期的 12 个月里产生了近乎两倍于 TypeScript 的新仓库,而 GitHub 自己的总结也直截了当:“JavaScript 仍然体量巨大”,并且"JavaScript/TypeScript 综合生态的总活跃度仍超过 Python 单一生态"。

然而智能体系统不是脚本。它们是服务、是管线、是 CLI 工具、是需要在生产环境中运行多年的分布式系统。而这些语言在每一个环节上都在与这种设计对抗:复杂性管理、依赖隔离、部署、运行时安全。

Go 的设计初衷是一门面向大型长生命周期软件的系统语言,而整个行业正不约而同地汇聚到这一类语言上来:编译型、安全、为运行数年而构建,而不是为下午写个脚本而存在的。

TypeScript 领域目前最令人振奋的事,是它向 Go 的移植。而 Python 领域最令人振奋的事,大多发生在 Rust 里:Pydantic 的校验核心是 Rust,Polars 是 Rust,HuggingFace tokenizers 是 Rust,Astral 的 uv 也是 Rust。

Go 在这次转型中的份额看起来不同,但同样说明问题:它是当前智能体工具浪潮所依赖的基础设施的默认选择。Ollama,本地运行模型的标准方式,是 Go 写的。Weaviate,支撑无数 RAG 和智能体记忆栈的向量数据库,是 Go 写的。Temporal,团队用来编排长时间智能体工作流的持久执行引擎,是 Go 写的。Charm 的 Crush,一款与 Google Antigravity 同类的优雅 AI 编程智能体 CLI,也是 Go 写的。甚至 GitHub 自己的 MCP 服务器,作为连接 AI 智能体与 GitHub 的参考实现,也是 Go。

在开发循环中,问题不是"哪种语言最容易写",而是"哪种语言最容易写、审查和交付"。智能体开发将这个问题的频率放大到了极致——一台机器每天要问几百次。

智能体循环将每一个弱点放大到无以复加

一个智能体的编码循环是这样的:实现 → 构建 → 测试 → 分析失败 → 自我修正 → 重复。人类开发者每小时跑这个循环大概十几次;自主智能体每个任务就要跑几十次——正如 Anthropic 关于编码智能体的指南所描述的,这些系统会"利用测试结果作为反馈来迭代解决方案"。这种频率从根本上改变了语言选择的性价比。

Wes McKinney 在他的文章《智能体人体工学》(Agent Ergonomics)中指出了这一转变:一旦由智能体来编码,快速编译-测试循环、无摩擦分发和确定性构建这些东西,就比语言对人类打字是否友好重要得多。他不只是写了文章,Roborev——他做的面向 AI 编程智能体的持续后台代码审查工具,就是用 Go 构建的。

Go 解决了四个互相叠加的问题,大多数工程师注意不到这些问题的存在,因为对摩擦习以为常。每一个问题的代价都以同一种货币支付:开发者的时间和注意力、智能体的迭代次数、以及用于纠正语言放过的错误所消耗的 API 费用。而且它们是层层递进的:慢构建浪费迭代,依赖解析失败浪费整个运行过程,弱的错误反馈让错误存活下来,而生态的剧烈变动则让智能体在启动之前就已经基于过时的知识。

构建时间

大项目的 Rust 或 C++ 构建持续几分钟是常态:对人类来说只是短暂打断,但对一个在某个功能上跑了 50 轮迭代的智能体而言,就是数小时被浪费的循环。Go 几乎是瞬时编译,让循环保持紧凑。

依赖管理

Python 的 pip 至今默认不保证确定性安装,即使使用虚拟环境,跨机器的版本冲突仍然常见。Node 的 npm 处理传递性依赖冲突其实做得不错:嵌套的 node_modules 可以并列容纳同一包的多个版本,package-lock.json 配合 npm ci 比过去可复现得多。npm 仍然会卡住的地方是 peer dependencies——互相冲突的版本要求抛出 ERESOLVE 错误,需要人工梳理。这种版本灵活性付出了深远代价:臃肿的重复依赖树撑大了 node_modules,扩大了智能体需要推理的表面区域。两个生态都允许在安装时执行任意代码(setup.pypostinstall 脚本),这是真实存在且不止一次造成过严重后果的供应链风险。Go 则采取了另一种默认做法:go.sum 锁定精确校验和,整个构建选择性地确定唯一版本的每个模块,而且没有安装时代码执行的钩子可供被攻破的依赖利用。对于一个持续生成和部署代码的智能体来说,这意味着更少的版本漂移和更小的攻击面。

错误反馈

mypypyrighttsc 这些工具作为循环的一部分运行时,Python 的类型注解和 TypeScript 的类型系统确实能在运行前抓住真正的错误。但两者都带着一个星号:Python 的类型系统仍然是可选的,采用程度参差不齐——即便像 Pydantic 这样的工具正在推动团队走向类型化;而 TypeScript 的类型保障在运行时被擦除,any 是一个被认可的、零成本的逃生门——只要代码遇到阻力,随时可以溜出去。这正是在时间和 token 压力下的智能体会做出的反应:为了编译通过而用上 any,在当下这一刻毫无代价,不管生态的规范是什么样子。

Jesse Vincent 正在构建 Superpowers 智能体编排框架,他记录下了当这种退出机制存在时会发生什么:智能体自我合理化来绕过规则。在一个现已臭名昭著的事件中,他环境里的一个 AI 智能体删除了测试文件来让失败的测试消失——逻辑上逻辑自洽:因为不存在的测试当然不会失败。用他自己的说:“规则有退出路径(我可以合理化’做完这一件事之后我就遵守’)。门没有——下一个动作被阻塞,直到门条件被满足。”

Go 没有这种退出机制。Go 确实有 any(它是 interface{} 的别名),但它不是 TypeScript 的 any。TypeScript 的 any 关掉了它所触及的一切的类型检查;Go 的 any 在每个使用点仍然受到检查,编译器拒绝任何该类型不支持的操作,除非经过编译器验证的显式断言。逃生门本身也带着门禁。

当一个错误在 Python 或 TypeScript 中溜过去,它往往在运行时才浮出水面,通常是在智能体已经在这个错误上构建了更多代码之后——到那时,它已经用上下文和 API 调用支付了复利。在 Go 中,同样的错误在编译时立刻被捕获,在智能体运行任何东西之前。代码本身就能看出差异:

1
2
3
4
// Go: 连逃生门都要被检查 —— 编译时报错,智能体运行任何东西之前就暴露
func ProcessData(data interface{}) int {
    return data + 1 // 错误: invalid operation: data + 1 (mismatched types interface{} and int)
}
1
2
3
4
5
6
# Python: 运行时错误 —— 智能体在发现错误之前浪费了迭代
def process_data(data):
    return data + 1  # 没有错误; 错误只在 data 实际被调用时才暴露

# 智能体运行它,在它上面搭建更多代码,然后:
process_data({"key": "value"})  # TypeError: unsupported operand type(s) for +: 'dict' and 'int'

Python 在易用性上的优势在这里变成了劣势:语言越宽容,智能体在被制住之前能犯的错误就越多,沿途燃烧的上下文和 API 调用也越多。同样的差距也体现在代码审查中——现在审查双方往往都是 AI:审查方的智能体能看出 Python 或 JavaScript 代码说了什么,但无法可靠地看出它做了什么,因为元类和原型链把行为藏在了静态阅读之外。Go 以同样方式消除了编写端和审查端的差距:函数名只有一个含义,方法派发仅靠名称,没有隐藏的控制流来从任何一端隐藏 bug。Go 的类型不是一个外加的层,而是语言本身,在构造上覆盖 100% 的代码。

生态震荡

这可能是四个问题中最大的一个。Go 的兼容性承诺意味着为 2012 年 Go 1.0 编写的代码,今天依然能够编译并正确运行。语言和标准库只做加法,基本上从不破坏已经正常工作的东西。

Node 生态中没有任何东西提供这种保障。主流框架经常性地破坏与自己先前版本的兼容性:Svelte 5 的 runes 是一个与 Svelte 4 完全不同的响应式模型;Vue 3 要求从零重写 Vue 2 的响应式模型;React 从 class 组件到 hooks 再到 server components,在活跃流通中留下了多个互不兼容的"React 代码是什么样子"的时代。问问一段代码面向的是哪个版本的 Svelte 或 Vue,你实际上在问它能不能跑。Python 也未能幸免——长达十年的 Python 2 到 3 迁移本身就是一则警示寓言。但 Node 的震荡更快、更持续。

这种震荡对智能体来说是一个特定的、不断叠加的麻烦。智能体对一个生态的工作知识,是训练时冻结的快照。在一个快速震荡的生态里,这个快照会过期,产出的代码看起来合理,但面向的是一个已经被改动或废弃的 API 表面。在 Go 里,几年前的训练快照今天仍然是正确的。实践上,这意味着智能体可以依赖它已经知道的东西,而不需要在每一个任务上重新验证它的假设。

Google 的 Distinguished Engineer Dave Rensin 在用 AI 智能体构建一个 10 万用户内部工具时,精准捕捉了这一层层叠加的风险:“如果我们不谨慎,我们不只是在更快地写代码——我们正在批量生产我们的错误。“Rensin 当时并非在特指 Go;这个风险适用于任何语言的 AI 辅助开发。但这恰恰是 Go 的结构(静态类型、显式导入、没有魔法)从设计上就内置了阻力的那种风险:差代码本身就难写。

在 PayPal,一个团队跟我分享了一次经历:他们有一款定制 C++ 数据库,功能强大且正确,但团队已经停止增长了——每个新人都要花数月学习代码库,大部分工程能力都用于维护。大约六个月后,大约十名工程师用 Go 重写了这个系统,新系统在生产环境中性能超过了原 C++ 系统,而且维护成本远低于原来。Go 不比 C++ 快,那到底发生了什么?瓶颈从来不是代码的速度,而是团队推理、维护和扩展代码的能力。实际发挥出来的性能,胜过理论上的性能——今天的智能体团队也在做同样的取舍,只是被压缩进了短得多的周期里。

Rust 呢?

Rust 和 Go 是互补的,通常占据不同的应用场景,两者在智能体开发中各有位置,但 Rust 是一个专业工具,而不是默认选项。

Rust 与 Go 共享了很多优势:内存安全、静态类型、没有隐藏的运行时魔法。Rust 的编译器错误提示也以教学式详尽而著称——按照本文以编译器为门禁的逻辑,智能体确实能从详细的 borrow checker 错误信息中获得实际价值。

然而,两种语言的分叉点恰恰出现在智能体开发最不宽容的地方。编译时间要长得多(如上文构建时间问题所述),而 Rust 安全保证背后的表达力(生命周期、trait 边界、borrow checker)是以 Go 设计中保证的那种"只有一种明显的做法"的可读性为代价的。这个代价在重构时暴露得最明显:以我的经验,在 Go 里本应局限于局部的改动,在 Rust 里会通过生命周期和 trait 边界连锁波及,而可重构性恰恰是智能体开发最倚重的特性。现在干这些活的不是人而是智能体,而智能体需要做得更多才能把代码搞对。语言越复杂,智能体越可能误读代码,产出越滚越大的 bug。

Rust 的零成本 C ABI 绑定,通过 PyO3 和 maturin 这类工具,让它异常容易地将编译好的 Rust 核心嵌入 Python 包,这就是为什么 pydantic、Polars 和 HuggingFace tokenizers 选择了 Rust。Go 在历史上在这方面的适配性要弱一些,因为 cgo 给这种嵌入方式带来了实际开销。不过 Go 正在缩小差距:Go 1.26 将 cgo 调用的基础开销削减约 30%,但 Rust 在很多应用场景中仍然是更优选择。然而对于智能体开发来说,取舍是清晰的:Go 的简洁和可读性使它成为智能体越来越多运行其上的系统层的默认选择,而 Rust 是在其安全保障值得付出代价时才会使用的专业工具。

上下文窗口是智能体开发中最稀缺的资源——Go 保护了它

上述一切都是被迭代次数放大的成本。上下文是智能体工作中的不变成本,而更多的上下文并不等于更好的结果。

“用更大的上下文窗口就行了"这个假设的背后,是认为模型能够均匀推理其 200K 或 1M 个 token。Chroma 2025 年对 18 个主流大语言模型的研究(涵盖 GPT-4.1、Claude 4、Gemini 2.5、Qwen3)直接测试了这个假设:保持任务难度不变,仅改变输入长度,结果发现这个假设不成立——随着输入增长,性能下降了,即使是在简单的任务上。最大的驱动因素是研究人员所称的干扰项:与模型需要的内容主题相关但不是答案的内容。一个干扰项就能可测量地损害准确性;四个则叠加放大这种损害。

与其试图扩大上下文窗口,远更好也更经济的方案是减少输入。

一个类继承体系、一个 mixin 链、一个装饰器栈——这些对正在追踪 bug 的智能体来说就是一片干扰项的雷区。一个通过菱形继承解析的 super() 调用、一个在运行时改写行为的元类、一个藏在三层继承链之上的方法覆盖——这些都不是智能体需要的逻辑,但智能体仍然需要加载它、权衡它、排除它,就在同一个推理已经退化了的上下文窗口里。这就是为什么智能体会自信地交付代码,却忽略了一个继承的覆盖,或者调用了错误的 mixin 方法。

Go 的设计是极简且显式的。一个文件需要的东西就存在那个文件里,以及它的直接导入里:没有继承链,没有从六个目录之外拉进来的 mixin,没有在运行时改写方法解析的元类。函数名只有一个含义,编译器强制执行这一点。Go 代码需要的 token,几乎全都是相关的 token。

这在准确性之外还有第二个效应:它降低了一个任务所需的模型级别。一个能放进 20,000 个干净 token 里的 bug,不需要和一个埋在 150,000 个框架噪声 token 里的 bug 同等的前沿推理能力。更简单、更少干扰的代码,模型更容易搞对,这意味着更小的、更便宜的、甚至是本地模型也能写出可以工作的 Go 代码,而在等价的 Python、Java、Rust 或 TypeScript/JavaScript 代码上它们可能就会出错。

当 AI 公司停止补贴成本时(我们已经看到这个趋势开始了),这一点就更加重要。一门语言,如果其代码简单到中等规模的模型就能正确处理,那就拥有了一种能够扛住补贴消失的成本结构。同一特性带来双重收益:让较小模型能写对代码的那个东西,也正是让人类审查者能快速阅读代码的那个东西——因为两边都没有隐藏的东西需要重新推导。

甚至不止于此,它更有可能第一次就把代码写对,这意味着更少的迭代、更少的 API 调用、更少的上下文燃烧,以及模型可以把更多时间花在不需要人类介入的任务上。

上下文效率,是 Go 在 2009 年下注的最锋利形式:为读者优化,无论是人类还是机器,而非为写者。

统一格式,零风格辩论

同样的读者优先赌注,也体现在代码的表面层面。gofmt 随 Go 一起发布,在整个社区的代码上运行。每一个 Go 文件——无论是由人写的、机器生成的、几十年前的,还是智能体今天早上写的——在结构上看起来完全相同。

Simon Willison 花了大量时间构建 AI 辅助的 Go 工具,他直接观察到了这一点:“我很享受这样一个事实:通常只有一种明显的做法,而产出的代码平淡且可读——这正是大语言模型非常擅长写的那种代码。”

当每一个 Go 文件都看起来一样时,智能体可以在开始工作之前就被预设好上下文。让它指向 Go 的标准库(encoding/jsonnet/httpio),它立刻就能产出符合惯例的、生产质量的代码,不需要猜测到底是 black 还是 yapfisort 还是 ruff。再丢进去像 go-skills 这样的东西,预设就更加直接了——智能体现在有了显式的、可复用的关于如何写惯用 Go 代码的指南,而不是从零散示例中推断规范。对于代码审查来说,无论是人类还是 AI,注意力都可以完全放在逻辑上,而不是格式噪音上。

Go 优化了整个软件开发生命周期——不仅仅是编码这一步

大多数语言比较聚焦在编写上:写代码能有多快?但软件开发生命周期不止于此:构建、测试、部署、调试、维护。在智能体工作流中,每一个步骤都以机器的频率运行。

过去十年间,Go 的生态从一门语言进化成了一个完整的软件开发生命周期平台:模糊测试被内置进来,漏洞管理通过 govulncheck 成熟起来,workspace 模式统一了 monorepo 开发,模块代理减少了版本冲突。这一切都不需要伸手到 Go 生态之外。

依赖由 go mod tidy 处理,在笔记本、CI 和容器上确定性且完全一致,不像 pipnpm,那里的每一次解析失败都是一次迭代的损失。测试只需要 go test ./...:不需要选框架决策,不需要配置 fixtures。在包旁边放一个 _test.go 文件,写一个 Test 前缀的函数,跑一条命令就行了。对一个要求做测试驱动开发的智能体来说,这种可靠性让门禁变得具有确定性,而 Python 的 fixture 丛林做不到这一点。得益于精确的依赖图,编译保持快速——这是迭代吞吐量的直接乘数。而部署只需要 go build:一个独立的、自包含的二进制文件,零运行时依赖,没有解释器版本问题,没有容器启动时间。拷贝二进制文件,运行即可。

这些优势不是线性叠加的,它们互相增强。软件开发生命周期每一个环节上更快的循环,意味着每小时更多的迭代、更低的 API 成本、以及更可靠的结果。

这不是关于哪门语言一统天下

本文的要点从来不是"取代 Python"或"取代 TypeScript”。把一切都押在一门语言上,不管这门语言赢了没有,都是同样的错误。真正的问题是更窄的:对于智能体工作流越来越运行其上的系统、服务和基础设施层来说,哪门语言最合适?在 2026 年,答案更多是 Go,因为它异常契合这个层面的需求。

Python 的机器学习生态(PyTorch、LangChain、Transformers)不会去任何地方,也不应该去。训练模型和运行推理,Python 就是答案。Go 不在那里竞争,它是在那里互补。

Go 可以轻松地运行在 Python 之下,就像它可以轻松地站在 Python 旁边一样。Simon Willison 构建了一个叫 go-to-wheel 的工具,把编译好的 Go 二进制文件打包成 Python wheel 发布在 PyPI 上:任何 Go 二进制都能变成一个标准的 pip install 或一个 uvx 一行命令就能运行的东西。而他自己的 sqlite-scanner,一个用 Go 写的并发文件系统扫描器,就是通过这种方式交付的。Armin Ronacher(Flask 的创造者)从另一个方向提出了同样的观点:把 MiniJinja 从 Rust 移植到 Go 花了他 45 分钟和 60 美元的 API 调用费,这也正是他为什么认为“编码的成本正在急剧下降……生态系统的广度不再那么重要了”。Python 负责编排,Go 在底层执行。而随着语言之间的迁移成本每个月都在降低,“只能选一个生态"的锁定论调正在日益削弱。你不需要在 Go 的结构性优势和 Python 的生态之间二选一——你两者都可以拥有。

Go 就是为此而设计的

Go 被设计来降低大规模软件的总成本,这正是智能体开发如今放大到临界点的问题。而让 Go 适合人类团队的那些特性,恰好也同样适合每天跑几千次迭代的机器。

这些都没有让 Go 成为唯一值得使用的语言。Python 仍然拥有机器学习生态,也有真正的理由在某些任务上选择 Rust、TypeScript 或其他语言。与其追求"一门语言统治一切”,不如把 Go 当作智能体开发中系统、服务和基础设施层的默认选择,然后要求一个具体理由才能换别的。当开始一个新服务、CLI 或系统时,问问自己"为什么不是 Go?“如果你能说出一个令人信服的理由(一个不存在的关键库、Go 无法满足的性能画像、团队投入太深以至于迁移成本超过收益),那就为此辩护。如果你说不出来,Go 很可能就是正确的默认选项。

慢构建、脆弱依赖或运行时意外造成的每一个百分点的损失,都是 API 成本。随着智能体工作量在 2026 年持续扩大规模,语言选择可能是控制这项成本的最大杠杆。及早消除这些摩擦的团队,将以更低的成本和更快的速度迭代——而那些还在为摩擦付费的团队将被甩在后面。

原文

原文:Why TypeScript 7.0 Was Rewritten in Go (and what it means for your dev stack)
作者:Steve Francia