做 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 年。
-
Graydon Hoare 回顾 Rust 的基础设施历程,Rust Foundation。
-
Mozilla welcomes the Rust Foundation,Mozilla,2021。
-
Announcing Rust 1.0,Rust 项目,2015-05-15。
-
Stability as a Deliverable,Rust 项目,2014。
-
What Is Ownership,The Rust Programming Language。
-
Unsafe Rust,The Rust Programming Language。
-
JVM 规范导论与 HotSpot 分层编译说明,Oracle Java 25;分层编译见页面对应小节。
-
Introduction to Garbage Collection Tuning,Oracle Java 25。
-
Data model与 Glossary,Python 3.14 文档。
-
C++ Core Guidelines,重点参考资源管理、错误处理与并发部分。
-
Virtual Threads,Oracle Java 25。
-
Fearless Concurrency、Shared-State Concurrency与 Races,Rust 官方文档。
-
CPython 的 GIL与 Python support for free threading,Python 3.14 文档。
-
Exceptions,Java 语言规范第 11 章。
-
Tokio tutorial,Tokio 项目。
-
typing,Python 3.14 文档。
-
What PyInstaller Does and How It Does It,PyInstaller 文档。
-
Error Handling与 Option,Rust 官方文档。
-
Spring Boot、PyTorch与 Introduction to Qt,各项目官方资料。
-
Codex CLI is Going Native,OpenAI Codex 官方仓库,2025-05-30。
-
Java 的 try-with-resources与 Python 的 with 语句;C++、Rust 资源管理另见资料 5、10。
-
Codex 开源仓库与 codex-rs 目录。开头只列出部分目录;本次本地阅读版本为
322bbf4d8486efd7dbbcf49598711a9e3fefc282,不代表公开仓库的最新版本。 -
References and Borrowing,The Rust Programming Language;借用与悬垂引用的规则说明。网页图片场景为教学示例,不是浏览器故障记录。
-
Experience Report: Developing the Servo Web Browser Engine using Rust,Servo 团队,2015;Fearless Security: Memory Safety,Mozilla,2019。用于核对浏览器核心的 C++ 背景、内存安全问题与 Rust 的处理方式。
-
JSON-RPC 2.0 Specification,见 Overview 与请求、响应定义:协议使用 JSON 消息,独立于具体传输方式。正文的 Python 应用示例用于解释跨语言通信,不作为 2025 年 Codex wire protocol 的具体接口说明。
我是程工造Agent,做 Agent 系统与 RAG 的工程师。这个专栏记录我从零学习 Rust、阅读 Codex 源码的过程。
本文部分内容包含 AI 辅助创作。