Zig Creator Andrew Kelley Talks Zig’s IO Design and Function Coloring Problem (2025-10-10, deepseek-chat)
1. 导读
本期播客的核心对话发生在 Zig 语言创始人 Andrew Kelley 与 Rock 语言开发者 Richard Feldman 之间。两位都是正在构建下一代系统级工具的实践者,他们的讨论并非泛泛而谈,而是深入到了编译器、运行时和语言设计的工程腹地。Andrew 作为 Zig 的掌舵人,其决策直接影响着这门旨在挑战 C 地位的语言的走向,而 Richard 正在将 Rock 编译器重写为 Zig,他的视角兼具使用者和深度改造者的双重身份。
对话的焦点看似是 Zig 近期引入的、要求所有 IO 操作必须显式传递 io 参数的重大设计变更,以及由此引发的“函数着色”争议。但这场讨论的真正价值远不止于此。它触及了现代编程语言设计中一系列根本性的张力:如何在提供强大抽象的同时不牺牲性能与明确性?如何平衡开发者的便利性与代码的可测试性及可靠性?当“零成本抽象”的教条遇到实际工程需求时,边界在哪里?对于任何关心语言设计、编译器技术或高性能软件架构的从业者而言,这场对话提供了来自一线构建者的、未经粉饰的思考与权衡。
2. 核心观点
Andrew Kelley 的核心世界观是:编程语言的首要任务是充当程序员与机器硬件之间的高效、无损耗的桥梁,任何设计决策都应服务于生成最佳机器码和提供最佳用户体验,而非迎合某种抽象的编程范式或理论纯洁性。这一世界观颇具争议,因为它明确将性能和控制力置于内存安全等传统“现代语言”特性之上,并认为通过严格的工程实践(而非类型系统)来保障正确性是可行且更优的路径。
IO 参数化是对“函数着色”问题的务实解耦,而非理论回避
Andrew 断言,通过要求传递 io 参数来抽象 IO 操作,Zig 实际上解决了“函数着色”问题的核心——即同步与异步代码的互操作性难题。其底层逻辑是:将 IO 的具体实现(同步、异步、测试模拟)推迟到运行时决定,而非在函数签名上硬编码颜色。这使得在调用链中切换 IO 执行模型(如从同步文件读取切换到异步网络请求)成为一个非破坏性变更,只需在调用处更换 io 参数的具体实例即可。这一设计在 Zed 编辑器(使用 Rust)的 FS 抽象和 Rock 语言的新编译器设计中得到了类似的印证,均是为了实现可测试性和执行模型灵活性。
“内存索引化”是达成极致性能与共享内存等高级优化的前提
Andrew 和 Richard 都强调,将编译器内部数据结构(如语法树节点)从指针引用彻底转换为基于偏移量的索引,是达成高性能缓存和复杂运行时优化的基石。其断言是:只有将每个编译单元(如模块)的内存布局压缩为少量(如20个)连续分配,才能实现高效的序列化/反序列化(如通过 pwritev 系统调用批量写入),并为进程间共享完整编译器状态(用于热重载)铺平道路。Rock 编译器向 Zig 的重写,核心驱动力之一就是为了全面拥抱这种“索引化”架构,以支持他们设想的、通过共享内存进行近乎零开销的热代码更新方案。
链接器不应是独立的“黑盒”,而应与编译器深度集成以实现增量链接 Andrew 挑战了将链接器视为独立工具、接受对象文件(object files)作为输入的传统模型。他断言,当编译器与链接器由同一工具链控制时,完全可以实现“增量链接”:在生成单个函数的机器码后立即将其写入最终可执行文件,并在函数修改后直接原地或异地修补二进制。其逻辑是,编译器完全掌握所有符号引用关系,无需依赖中间对象文件格式作为中介。这不仅大幅提升构建速度,其技术实质(在磁盘或内存中修补函数体)与“热代码交换”(hot code swapping)有99%的重合,为开发期和生产期的动态更新打开了大门。
“未使用变量”等警告必须默认为错误,以构建可靠的开发流程 Andrew 坚决主张,像“未使用变量”这类在 C/C++ 中常被忽略的警告,在 Zig 中必须默认为编译错误。其底层逻辑源于对传统构建流程不可靠性的深刻反思:警告容易被忽略、构建系统会缓存成功状态导致警告消失,最终导致问题被带入生产环境。他将此视为开发流程“非功能性”和“可变状态”问题的体现。Richard 透露 Rock 也独立得出了相似结论:所有问题和警告都导致非零退出码,但编译器仍会生成可执行文件(运行时对应位置会 panic),以此平衡“开发期流畅”与“发布前必须修复”的要求。
SIMD 与定制数据结构可超越通用哈希表,成为特定场景的性能利器
Richard 提出了一个具体性能假设:在编译器字符串驻留(interning)这类场景中,针对短标识符长度分布特征设计的定制数据结构(如按长度分桶的数组),结合 SIMD 指令进行并行查找,其性能将远超通用的哈希表。这一断言基于他在之前 Rust 编译器中的观察:即使简单的线性数组搜索,在条目数达到数十万之前也常常比哈希表更快。Andrew 回应指出 Zig 的 ArrayHashMap 已内置了线性扫描阈值,并鼓励对此进行实验。这体现了双方共同的信条:针对问题域特性进行特化优化,比依赖通用抽象更能榨取硬件性能。
这些观点共同勾勒出一条清晰的主线:从语言语义设计(IO参数化)、到编译器内部表示(内存索引化)、再到工具链集成(增量链接)和开发流程规范(警告即错误),Andrew 和 Zig 的选择始终服务于同一个目标——为追求极致性能、可控性和可靠性的系统程序员提供一套“不留性能遗憾”且工程实践严谨的工具链。其内在逻辑是层层递进的:只有基础表示足够紧凑和确定,才能实现高效缓存和共享;只有工具链深度整合,才能解锁增量与动态优化;而所有这些优化,最终都需要一个不允许马虎的严格开发环境来保障其正确性。
3. 批判与质疑
Andrew 和 Richard 的论述体系建立在几个关键前提之上,这些前提值得用批判性眼光审视。
首先,“内存索引化”与进程间共享内存的愿景严重依赖特定系统特性且复杂度极高。整个设计美妙地统一了磁盘缓存和内存共享,但其正确性完全依赖于手动管理那“20个指针”的偏移量修复。一处偏移计算错误就会导致内存破坏,而这类错误在共享内存场景下可能表现为极其诡异的跨进程相互影响,调试难度极大。此外,其高效序列化依赖 pwritev 等系统调用,在 Windows 上的可行性存疑(Andrew 承认 Windows 缺乏等效方案),这损害了 Zig 标榜的跨平台一致性。将整个编译器状态置于共享内存以实现热重载,更是将巨大的状态复杂度暴露给了并发环境,其正确性验证将是一个巨大挑战。
其次,对“未使用变量”等问题的零容忍策略,可能过于理想化而忽视了开发流程的多样性。强制将所有警告视为错误,固然能杜绝代码“带病上岗”,但也可能在某些快速原型或探索性编程场景中制造摩擦。虽然 Rock 采用“生成代码但非零退出码”的折中方案,但这仍要求开发者或 CI 系统区分“可暂时接受的问题”和“必须阻止的问题”,增加了心智负担。这种严格性是否会将一部分偏好更宽松、迭代更快工作流的开发者拒之门外,是一个开放问题。
再者,关于“函数着色”问题的解决方案,其普适性有待检验。Andrew 认为 IO 代码通常集中在代码库的特定部分,因此传递 io 参数的影响有限。这一判断可能高度依赖于 Zig 当前的应用生态(如编译器、嵌入式系统)。如果 Zig 未来希望进军需要密集、分散 IO 的领域(如 Web 服务器、数据库),那么“传递额外参数”所带来的 API 变更成本可能会被放大。此外,该方案主要解决了同步/异步抽象,但对于 Loris Cro 提出的“异步性”与“并发性”的微妙区别(如单线程内客户端/服务器死锁问题),Zig 的新 IO 模型是否提供了足够精细的控制原语,对话中并未深入探讨。
最后,整个高性能愿景对“正确性”的保障高度依赖测试文化,而非类型系统。Andrew 明确表示,在拥有完备模糊测试(fuzzing)等强测试覆盖的前提下,他对 Zig 缺少 Rust 那样的内存安全保证并不太担心。这是一个重大的工程赌注。它假设团队能建立并维持极其严格的测试实践,并且模糊测试等动态方法能覆盖指针误用、数据竞争等深层内存错误。对于资源有限或测试文化不强的项目,Zig 提供的安全网要比 Rust 稀疏得多。
4. 行业视野
这场对话并非孤立的技术探讨,而是系统编程语言复兴浪潮中的一个鲜明注脚。它清晰地与行业内的几股重要趋势形成呼应与对抗。
首先,它直接挑战了以 Rust 为代表的“安全至上”类型系统范式。Rust 通过所有权和借用检查器在编译期提供强大的安全保障,但 Andrew 和 Richard 都表达了在这种范式下进行激进内存布局优化(如共享内存、指针重定位)时所感受到的“心智负担”和“约束感”。Zig 的选择代表了一条不同的道路:将控制权完全交还程序员,依靠清晰的约定、严格的代码规范和动态检查工具来达成可靠性与性能的双重目标。这仿佛是 C 语言哲学在 21 世纪的精致化重现,与 Go 的“简单性”和 Rust 的“安全性”形成了三足鼎立的思想阵营。
其次,它印证了“工具链垂直整合”以提升开发者体验的深层趋势。从 Zig 和 Rock 都计划将编译器与链接器深度整合以实现增量链接和热重载,到强调编译缓存命中性能是大型项目体验的关键,都说明现代语言项目正在从“提供语法和标准库”向“提供高度集成、智能化的完整开发套件”演进。这与 Apple 的 Swift、Google 的 Bazel 构建系统所体现的思路一脉相承,即通过工具链的深度协作来管理复杂度、提升效率。
再者,对话中关于异步编程模型的讨论,触及了当前并发编程领域的核心困惑。从 Node.js 的“函数着色”,到 Go 的 goroutine,再到 Rust 的 async/await,每种模型都在尝试平衡表达力、性能和易用性。Andrew 将 IO 执行模型参数化的方案,可以看作是一种更底层的、将调度决策权上交的尝试。而 Loris Cro 试图区分“异步性”与“并发性”的努力,则反映了社区正在努力为这些模糊概念建立更精确的语义模型,以期实现更可组合、更少陷阱的并发抽象。
最后,对极致性能的追求,特别是利用现代 CPU 特性(如 SIMD)和定制数据结构的思路,反映了高性能计算和系统软件领域对“通用解”的不满足。当哈希表这样的通用数据结构成为瓶颈时,回归问题本质,设计特化解决方案,正成为顶级性能敏感项目(如游戏引擎、数据库、编译器)的常见手段。Zig 和 Rock 作为系统语言,其设计哲学本身就鼓励和赋能了这种“向下挖掘”的编程风格。
5. 启示与建议
这场对话首先挑战了一个广泛存在的假设:“内存安全必须通过编译时类型系统来保障”。它强化了另一个假设:“通过严谨的工程实践、全面的测试和清晰的约定,同样可以构建出高可靠性的系统软件”。
对于语言设计者与编译器开发者:
- 深入评估“内存索引化”架构:认真研究将指针密集型数据结构转换为基于偏移量的索引这一模式。即使不追求进程间共享,这对于实现高性能的增量编译缓存和快速序列化也可能有巨大收益。可以从小型模块或中间表示开始试点。
- 将模糊测试(fuzzing)提升为核心基础设施优先级:如果选择 Zig 这类将安全责任更多下放给开发者的道路,那么必须投资构建一流、易用的模糊测试工具链。这包括对生成器(smith)的支持、代码覆盖引导以及与构建系统的无缝集成。
对于寻求高性能与可控性的系统程序员:
- 在项目早期引入“依赖参数化”模式:无论是 IO、分配器还是其他外部服务,考虑像 Zig 的
io/allocator参数一样,将依赖作为显式参数传递。这不仅能极大提升代码的可测试性(通过模拟实现),也为未来切换底层实现(如同步/异步)提供了灵活性。可以从项目的基础设施层开始实践。 - 敢于为关键路径设计特化数据结构:当性能分析表明通用数据结构(如哈希表、动态数组)成为瓶颈时,不要畏惧基于数据的具体特征(如键长分布、访问模式)设计定制解决方案。SIMD 指令集是提升线性扫描类操作性能的强大武器。
需要谨慎对待的推断:
- “共享内存热重载方案”是强信号的技术愿景,展示了极致的性能想象力,但其工程复杂度和正确性挑战极高,在短期内更应视为一个研究方向而非可立即采纳的成熟方案。
- “警告即错误”的文化是强信号的最佳实践建议,已被证明能有效提升代码质量,但在引入团队时需要配套的流程和文化适应。
- “Zig 范式可完全替代 Rust 的安全性” 只是一个合理推断,其成立高度依赖于项目团队对测试、代码审查和工程纪律的投入程度。对于资源有限或安全至上的项目,Rust 的编译时保障仍然是更稳妥的默认选择。
6. 金句摘录
“if you want to have stuff testable, you need to do something like this or something that’s like more complicated and in my opinion worse.” (“如果你想让代码可测试,你需要这样做,或者做一些在我看来更复杂、更糟糕的方案。”) 语境:Andrew 在解释 Zig 要求传递
io参数的设计时,对比了依赖注入等复杂模式,认为显式传递参数是最简单、最直接的实现可测试性的方式。
“the zig philosophy is like look you have a computer your interface to that computer is machine code. Uh we are going to help you write the best possible machine code that you can possibly write.” (“Zig 的哲学是:你看,你有一台计算机,你与它的接口是机器码。我们将帮助你编写你能写出的最好的机器码。”) 语境:Andrew 阐述 Zig 的核心设计原则,强调语言的目标是成为程序员与硬件之间高效、无损耗的桥梁,不为了抽象而牺牲任何可能的性能。
“It’s like if your anti virus software is uh interfering with your software development, that is a skill issue.” (“这就好比如果你的杀毒软件干扰了你的软件开发,那是你的技术有问题。”) 语境:在讨论通过内存补丁实现热重载可能触发反病毒软件误报时,Andrew 表达了对其干扰开发流程的强烈不满,反映了他对开发环境应完全受控的极端态度。
“I think this kind of a tell, isn’t it?” (“我觉得这有点暴露本质了,不是吗?”) 语境:Richard 提到 Rust 中
unwrap的另一种形式expect允许添加自定义错误信息,Andrew 敏锐地指出,这反而更明显地暴露了代码中可能存在 panic 的风险点,反映了他对 API 设计引导开发者行为的重要性的关注。
“And then I have all these people complaining at me in Zig about like unused variables like so like it’s like these people can’t agree with each other, right?” (“然后就有这么多人在 Zig 里跟我抱怨未使用变量的问题……你看,这些人彼此之间都无法达成一致,对吧?”) 语境:Andrew 分享了一个故事,他因将一段有未使用变量 bug 的 C 代码移植到 Zig 并让 Zig 捕获了该 bug 而受到批评,批评者认为 C 编译器也能警告。他指出了其中的矛盾:抱怨 Zig 严格的人,往往也抱怨 C 默认不严格导致问题被忽略。