夜雨聆风学习资料网

ARTICLE · 1199633

从 0 到 1 学 Codex 源码:从 Rust 开始

从 0 到 1 学 Codex 源码:从 Rust 开始

做 Agent 开发,我一直想把 Codex 的核心代码认真读一遍。

用户输入一句需求后,它怎么调用模型,怎么执行工具,又怎么把这些步骤串成一轮完整的任务?这些都是我想顺着源码弄明白的事。

但一上来,我就被 Rust 劝退了。

Java 学过了,Python 学过了,TypeScript 也学过了,现在为了读 Codex,又得学 Rust。真是一山更比一山高。

想研究的明明是 Agent,结果还没走到核心逻辑,先得补一门语言。估计不少人和我一样,看到一堆不熟悉的语法,学习计划就先往后放了放。

所以,我打算开一个「Codex 源码学习」专栏,给自己一个把这件事做下去的理由:从零学 Rust,把读 Codex 时遇到的问题和理解过程写下来。遇到熟悉的概念,就拿 Java、Python 来对照;遇到没见过的东西,再停下来补课。

如果你也没学过 Rust,但又想读懂 Codex 的源码,我们可以一起从这里开始。

先看一眼我们准备读的代码放在哪里。下面从 Codex 仓库里挑了几个目录,后面还会逐个走进去:[25]

codex/├── codex-rs/          # Rust 实现,接下来重点读这里│   ├── cli/          # 命令行入口│   ├── core/         # Codex 的核心业务逻辑│   ├── tui/          # 终端里的交互界面│   ├── exec/         # 非交互执行,例如 codex exec│   └── app-server/   # 供客户端调用的服务接口├── sdk/│   └── typescript/   # TypeScript SDK└── docs/             # 项目文档

先记住 codex-rs/ 就够了。我们要研究的模型调用、工具执行和任务协作,都会从这里往下找。目录里还有 TypeScript SDK,但要继续深入 Rust 核心,语言这一关还是得过。

这一篇先不急着背语法。我想先弄清楚:Rust 是怎么来的?已经有 Java、Python 和 C++,它解决了什么问题?Codex 又为什么选了它?

Rust 是怎么来的

Rust 最早是 Graydon Hoare 在 2006—2009 年间探索的个人项目。2009 年,开发 Firefox 浏览器的 Mozilla 开始投入;2010 年,Rust 公开亮相。[1]、[2]

当时,Firefox 的核心渲染引擎用 C++ 编写。C++ 能精细控制内存,但使用和释放数据的关系一旦处理不当,就可能造成崩溃,甚至安全漏洞。[27]

举个简化的例子:网页还在加载图片,你把它关了。如果清理代码释放了图片内存,后台任务却继续按旧地址读取,就可能读错数据甚至崩溃。这叫“释放后使用”。[26]

Rust 用所有权和借用检查约束这件事:谁负责这份数据,谁还在借用它,都要在代码里表达清楚。在安全 Rust 中,如果写成“先释放图片,再通过借用读取”,编译器就会报错,让开发者先修正这个问题。[5]、[26]

这也是 Rust 吸引人的地方:保留对底层资源的控制,同时让编译器帮忙检查容易出错的内存使用方式。

2015 年 5 月 15 日,Rust 1.0 发布。从此,它有了稳定版本和兼容性承诺,新特性也能通过不同发布通道继续演进。[3]、[4]

图 1:Rust 从早期探索到稳定版本的几个节点。

Rust 的核心特点

前面的图片例子里,Rust 会在程序运行前检查数据的使用关系。要做到这一点,它对数据由谁管理、其他代码怎样使用数据,都有明确的规则。

换成一个更接近 Codex 的场景:程序把项目文件读进内存,再把内容交给另一个函数分析。

这时,谁负责保存这段内容?分析函数能不能修改它?原来的函数还能不能继续用?等大家都不用了,谁来释放内存?

写 Java 或 Python 时,我们通常不用在每个函数旁边想一遍这些问题。常见的 JVM 有垃圾回收器,CPython 则主要使用引用计数,再配合循环垃圾回收,帮我们管理对象占用的内存。[8]、[9]

现代 C++ 的常用做法是把资源交给对象:对象结束生命周期时,由析构函数清理。这种做法叫 RAII,容器和智能指针也能帮开发者完成大量管理工作。[10]

Rust 引入了所有权和借用,并让编译器检查相关规则。暂时可以这样理解:所有权说明这份值归谁负责;借用则是让别的代码在约定范围内使用它。[5]

还是刚才的文件。如果分析函数只需要读取内容,就可以借用它。编译器会检查这份内容活得够不够久,避免函数拿着引用时,数据已经失效。

麻烦也在这里。有些写法在我们看来很自然,编译器却不接受,得重新考虑数据放在哪里、由谁持有、怎么传给下一步。学过其他语言,到了 Rust 仍然要适应这一套思路。

好处是,这些问题有机会在编译时就被拦下来,不必等程序跑到某个角落才暴露。当然,业务逻辑写错、两个任务互相等锁,仍然得自己排查。Rust 程序运行时也需要分配内存、释放资源;用到引用计数或异步运行时,还会有相应的开销。[5]、[13]

以后读源码还会碰到 unsafe。它允许进行一部分编译器无法保证安全的操作,需要实现者自己满足安全条件;其他 Rust 检查仍然有效。我们读到具体代码时,再看它为什么需要这么写。[6]

图 2:编译时检查使用规则,和运行时释放内存,可以同时发生。

放在同一个任务里,比较四种语言

为了不把比较写成一堆抽象术语,我们给自己设一个任务:写一个本地命令行助手,能读项目文件、调用远端模型、执行命令,再把结果显示出来。

这个任务和我们想研究的 Codex 很接近。下面就沿着写出这个工具、把它交给别人使用的过程,看看四种语言分别要操心什么。

Java 这里主要讨论 HotSpot JVM,Python 主要讨论 CPython。下面一些行为属于具体实现,换一种运行方式,结论也可能变化。

1. 程序怎么跑起来

Java 常见的做法,是先把源代码编译成字节码,再交给 JVM 执行。HotSpot 会结合解释执行和即时编译(JIT),在运行过程中把代码编译成机器码并优化。[7]

CPython 也会生成字节码,再由解释器执行。不过,调用一个 Python 库时,里面的计算可能已经交给 C、C++ 等语言写的原生代码了。因此,不能仅凭入口是 Python,就认定所有工作都由 Python 解释器完成。[9]、[14]

C++ 和 Rust 的常见做法,则是提前编译、链接,生成目标平台的原生程序。[11]

对这个命令行助手来说,差别会体现在用户第一次启动它的时候:需要事先安装什么,安装包里又得带上什么。Java 可以选择 GraalVM Native Image,Python 也有把解释器一起打包的工具。这些办法,我们到交付部分再说。[18]、[19]

2. 数据用完了,谁来清理

Java 的 GC 会检查哪些对象还能被访问,回收已经不可达的对象所占用的内存。CPython 通过引用计数和循环垃圾回收管理对象。两者都能省去不少逐个释放内存的工作。[8]、[9]

但如果助手把所有读过的文件一直存在缓存里,垃圾回收器也不会替我们判断“这些内容其实已经没用了”。只要引用还在,内存就可能一直占着。

C++ 可以用 RAII、容器和智能指针自动清理资源。Rust 则通过所有权安排管理责任,在相应生命周期结束时执行清理;需要多处共同持有数据时,也有引用计数这样的工具。[5]、[10]、[13]

还有文件、连接和锁。它们的释放不能简单等同于内存回收:Java 有 try-with-resources,Python 有 with,C++ 和 Rust 也可以借助对象或值的生命周期完成清理。写代码时仍要看具体 API 的约定。[24]

所以,读到一段文件处理代码,我会关心两件事:内容保存到哪里,文件又在哪里关闭。换一门语言,这两个问题都还在。

3. 说“快”的时候,究竟在说什么

一个助手启动很快,不代表它扫描大项目也快;常驻运行时吞吐很高,也不代表第一次启动就能马上响应。这里至少有启动时间、持续计算速度、内存占用和等待时间几件事。

Java 要留意启动和 JIT 热身之后的差别。Python 要看耗时发生在解释器里,还是已经进入了原生扩展。C++ 和 Rust 给了开发者较多控制数据布局、分配和调用开销的机会,但算法、数据结构和库的选择仍然会影响结果。[7]、[14]

放回我们的助手,可能大部分时间都在等远端模型回复。本地程序换成 Rust,也不会让远端模型因此生成得更快。如果慢在反复读取和复制文件内容,就该先查看这一段代码。

我现在更想学会的是找出这些耗时发生在哪里。光凭语言名字排一个快慢顺序,帮不了我们理解 Codex。

4. 等模型的时候,还能不能干别的

模型还没返回,终端要继续显示输出,用户也可能随时按下取消。程序得让这些任务一起推进。

多个任务交错推进,叫并发;多个任务在不同核心上同时执行,则涉及并行。

Java 有线程、线程池,也有虚拟线程。虚拟线程适合组织大量需要等待的任务,但不会直接加快一段 CPU 计算。[12]

Python 可以用线程、进程和 asyncio。经常被提到的 GIL,要放在具体的 CPython 构建里看:带 GIL 时,同一时刻通常只有一个线程执行 Python 字节码,但 I/O 和部分原生扩展可以释放它。从 Python 3.13 开始,还有可选的自由线程构建,使用时需要检查第三方扩展的支持情况。[14]

C++ 提供线程、锁和原子操作等工具。Rust 也支持线程、消息传递和共享状态,并通过所有权和类型约束检查数据能否跨线程使用;写异步程序时,还经常会碰到 Tokio 这样的运行时。[10]、[13]、[16]

这些工具能帮我们组织任务,具体怎么退出还得设计。比如用户取消了请求,已经启动的工作在哪里停下来?后面读 Codex 时,这就是值得追的一条线。

图 3:执行方式、内存、性能和并发放在一起看。

5. 出错以后,代码怎么往下走

给读取文件的函数传错参数类型,和参数正确但文件不存在,是两种不同的错误。

Java 能在编译时检查许多类型问题,读取失败则可以通过异常处理,其中一部分异常有编译期检查要求。C++ 也有静态类型检查,运行中的失败可以用异常、错误码或结果类型表达,取决于项目和接口的约定。[10]、[15]

Python 中不少错误,要执行到那一行才会暴露。类型标注可以配合 IDE 和静态检查工具提前找问题,但 Python 运行时本身不会按这些标注强制检查每次调用。[17]

Rust 常用 Result 表示成功或失败,用 Option 表示有值或没有值。[20] 文件不存在,也不会因为 Rust 编译通过就消失,程序仍然得决定接下来怎么办。

以后在 Codex 中看到 Result,我们就顺着它看:这里失败后,是重试、返回上层,还是显示给用户?先知道它在处理什么,语法再慢慢补。

6. 写起来和改起来,哪一步费劲

对我来说,Java、Python、TypeScript 至少已经学过。现在换到 Rust,最直接的成本就是重新熟悉语法、工具,以及前面说的所有权。

Java 项目常用 Maven 或 Gradle 管理构建和依赖,IDE 能借助静态类型做分析和重构。Python 修改脚本、运行、看结果的过程很直接,但环境、依赖和测试仍要自己安排。

C++ 项目里,编译器、构建系统、库和链接关系都可能需要协调。支持的平台越多,这部分工作通常越值得留意。

Rust 的 Cargo 把依赖管理、构建、测试等常用操作放到了一套工具里,入门时少了一些到处找工具的麻烦。[21] 但真正写起代码,理解所有权、解决类型报错、等待编译,还是得花时间。

我暂时会把目标放在能读懂一小段真实代码上。先知道它做什么,再看数据怎么传、失败怎么处理,比一上来就要求自己熟练写 Rust 更容易开始。

7. 有哪些现成的东西可以用

选语言时,很少有人愿意把所有东西从头写一遍。

Java 有 Spring Boot 这样的应用框架;Python 有 PyTorch 等机器学习工具;C++ 可以使用 Qt 和大量已有的原生库;Rust 则有 Tokio 这样的异步组件,并用 Cargo 管理依赖。[16]、[21]、[22]

这些工具也会跨语言合作。比如 PyTorch 有 Python 接口,也有 C++ 接口,一个看起来是 Python 的项目,底层可能运行着大量原生代码。

回到命令行助手,要找的东西就更具体了:有没有合适的模型客户端?终端界面用什么画?调用系统功能是否方便?这些依赖有没有持续维护?

Rust 的语言特性再合适,如果某个关键依赖接起来很麻烦,开发时间一样会花在这里。后面读 Codex 的依赖列表,也是在看它借用了哪些现成工具。

8. 怎么让别人也能运行

在自己的电脑上跑通以后,还要考虑没有开发环境的用户。

Java 可以要求用户准备运行环境,也可以用 jpackage 把运行时一起带上。GraalVM Native Image 则提供了生成原生程序的另一条路,需要检查它的构建和兼容条件。[18]

Python 可以在已有解释器的环境里运行,也可以用 PyInstaller 把程序、解释器和依赖打包起来,让用户不用单独安装 Python。[19]

C++ 和 Rust 通常交付为目标平台编译好的原生程序。但 Windows、macOS、Linux,以及不同 CPU 架构,仍然需要分别考虑,动态库和其他原生依赖也要处理。

用户看到的可能只是一个下载按钮,开发者背后却要准备多种构建,做好测试和更新。对 Codex 这样的本地工具来说,少让用户折腾一次运行环境,就是很实际的改进。

图 4:写代码时要用的工具,以及交付时要准备的东西。

Codex 为什么选择 Rust

2025 年 5 月 30 日,维护者在官方仓库发布了《Codex CLI is Going Native》,介绍从 TypeScript 实现推进 Rust 重写的工作。[23] 这里讨论的是运行在用户电脑上的命令行工具,要考虑用户怎么安装、程序怎么执行命令,以及其他应用怎么接入。

安装更省事:用户拿到的是编译好的程序。

当时的 TypeScript 实现依靠 Node.js 运行,要求用户准备 Node.js 22 及以上版本。用户只是想用 Codex,却可能要先安装或升级 Node.js,才能启动它。

Rust 的常见做法是由发布者提前编译,把源代码变成对应操作系统和 CPU 能运行的程序,例如 Windows 上的 .exe。用户直接下载匹配的版本,由操作系统加载运行,不需要先安装 Rust 编译器,也不需要 Node.js 来执行这个原生程序。[11]

省掉的就是“先准备合适的 Node.js 环境”这一步。如果选择 npm 安装,仍然需要 npm 及其运行环境;原生程序让直接分发可执行文件成为一种选择。C++ 同样能这么交付,Java、Python 也有打包运行环境的方案,所以这里的改善针对的是 Codex 当时的安装方式。[18]、[19]

接入系统安全功能:项目已经有了 Rust 实现。

Codex 会启动命令,沙盒可以限制这些命令能写哪些目录、能否联网。这些限制需要调用操作系统的安全机制。公告提到,当时 Linux 沙盒已经用 Rust 实现,团队已经有可用的代码和接口。Rust 的内存安全检查与沙盒的权限限制各有用途,前者不会自动替代后者。[23]

改善性能和内存占用:控制本地程序的开销。

维护者把无需运行时垃圾回收、降低内存消耗列为迁移动机。[23] Rust 通过所有权等机制管理资源,不依赖通用追踪式垃圾回收器,同时在编译时检查一部分内存使用规则。[5] 这给了团队优化本地执行核心的空间,具体改善多少仍要测量。

其他语言仍能接入:这是协议设计的作用。

公告还提到,团队希望通过通信协议,让 TypeScript、JavaScript、Python 等语言参与扩展。[23]

比如,一个 Python 应用按约定的消息格式,把任务交给 Codex,再接收执行结果。只要双方遵守协议,就不必用同一种语言编写。Java、Python、TypeScript 都能做 RPC。以 JSON-RPC 为例,协议约定的是请求、结果和错误的消息格式,双方可以用各自的语言实现。Rust 在这一点上没有独有优势。[28]

因此,这一项说明的是:核心改用 Rust,外部应用仍然可以用熟悉的语言与它通信。 运行这个 Python 应用仍需要 Python 环境,协议不会替它消除运行依赖。它解决的是不同程序如何协作,而前面几项才是在解释团队为什么想用 Rust 实现核心。

接下来,边学 Rust 边读代码

学一门新语言确实费时间。我也不打算等到“Rust 全学会了”,才开始碰 Codex。

接下来安排三篇基础篇,每篇都从 Codex 里挑一些代码来读:

  • 第一篇先找项目入口,认识基本语法、结构体、枚举和分支,弄清楚一段代码大概在做什么。
  • 第二篇看所有权、借用和错误处理,追踪数据传给了谁,失败又传到了哪里。
  • 第三篇看接口实现、异步任务和消息通道,试着把几段代码的协作过程连起来。

用得上 Java、Python 的经验,就拿来对照;遇到 Rust 特有的写法,再多花一点时间。之后再沿着调用关系,逐步拆开 Codex 的核心实现。

下一篇就打开项目,从入口和一小段 Rust 代码开始。先读懂一点,再往里走。

参考资料

本文查阅的资料列在下面,方便继续阅读。语言机制与目录核对于 2026 年 10 月 15 日,Codex 迁移公告发表于 2025 年。

  1. Graydon Hoare 回顾 Rust 的基础设施历程,Rust Foundation。
  2. Mozilla welcomes the Rust Foundation,Mozilla,2021。
  3. Announcing Rust 1.0,Rust 项目,2015-05-15。
  4. Stability as a Deliverable,Rust 项目,2014。
  5. What Is Ownership,The Rust Programming Language。
  6. Unsafe Rust,The Rust Programming Language。
  7. JVM 规范导论与 HotSpot 分层编译说明,Oracle Java 25;分层编译见页面对应小节。
  8. Introduction to Garbage Collection Tuning,Oracle Java 25。
  9. Data model与 Glossary,Python 3.14 文档。
  10. C++ Core Guidelines,重点参考资源管理、错误处理与并发部分。
  11. GCC Overall Options与 Rust 的编译和运行。
  12. Virtual Threads,Oracle Java 25。
  13. Fearless Concurrency、Shared-State Concurrency与 Races,Rust 官方文档。
  14. CPython 的 GIL与 Python support for free threading,Python 3.14 文档。
  15. Exceptions,Java 语言规范第 11 章。
  16. Tokio tutorial,Tokio 项目。
  17. typing,Python 3.14 文档。
  18. jpackage与 GraalVM Native Image。
  19. What PyInstaller Does and How It Does It,PyInstaller 文档。
  20. Error Handling与 Option,Rust 官方文档。
  21. The Cargo Book。
  22. Spring Boot、PyTorch与 Introduction to Qt,各项目官方资料。
  23. Codex CLI is Going Native,OpenAI Codex 官方仓库,2025-05-30。
  24. Java 的 try-with-resources与 Python 的 with 语句;C++、Rust 资源管理另见资料 5、10。

  25. Codex 开源仓库与 codex-rs 目录。开头只列出部分目录;本次本地阅读版本为 322bbf4d8486efd7dbbcf49598711a9e3fefc282,不代表公开仓库的最新版本。

  26. References and Borrowing,The Rust Programming Language;借用与悬垂引用的规则说明。网页图片场景为教学示例,不是浏览器故障记录。

  27. Experience Report: Developing the Servo Web Browser Engine using Rust,Servo 团队,2015;Fearless Security: Memory Safety,Mozilla,2019。用于核对浏览器核心的 C++ 背景、内存安全问题与 Rust 的处理方式。

  28. JSON-RPC 2.0 Specification,见 Overview 与请求、响应定义:协议使用 JSON 消息,独立于具体传输方式。正文的 Python 应用示例用于解释跨语言通信,不作为 2025 年 Codex wire protocol 的具体接口说明。

我是程工造Agent,做 Agent 系统与 RAG 的工程师。这个专栏记录我从零学习 Rust、阅读 Codex 源码的过程。

相关学习资料