理解 Zig:现代系统编程语言与工具链
编者注: 本文初稿由 AI 辅助完成,具体来自一次与 ChatGPT 的对话,随后用 Claude 校订事实准确性。如果发现错误或过时内容,请留言告诉我。我不是 Zig 专家,而这门语言也仍在积极演进。
过去几年,Zig 在系统编程圈中越来越受关注。人们常将它称为 C 的现代替代品,但这门语言及其生态实际希望解决的问题更广:简化系统编程工具链、改进交叉编译,以及让开发者明确控制性能和并发。
本文概述 Zig 是什么、如何工作、与 Swift 等语言有何区别,以及它为什么在基础设施和系统工具领域受到青睐。
Zig 是什么?
Zig 是 Andrew Kelley 在 2016 年前后创建的系统编程语言。设计目标包括:
- 接近 C 的性能
- 显式内存管理
- 尽量低的语言复杂度
- 没有隐藏的控制流
- 现代化的集成工具链
Zig 的理念是,让程序员完全掌控程序行为,同时去掉 C 生态几十年来积累的复杂性。
一种常见描述是:
C++
↓ remove complexity
C
↓ modernize
Zig
不过,这种说法稍微低估了 Zig。comptime 和结构化错误处理等特性,赋予了它明显超出 C 的能力。它不只是简化,更是一种重新思考。
Zig 并不试图取代 Rust 或 Go 这样的语言。它公开表述的目标更具体:
取代 C,成为编写系统软件的主要语言。
Zig 与 LLVM
Zig 使用 LLVM 作为后端编译器基础设施。
编译流水线大致如下:
Zig source code
↓
Zig frontend (parsing + semantic analysis)
↓
LLVM IR
↓
LLVM backend
↓
Machine code
这与其他一些语言类似,例如:
- Swift
- Rust
- C/C++(通过 Clang)
但共同使用 LLVM,不代表编译器可以互换。LLVM 只负责生成优化后的机器码,每门语言仍需要自己的前端和运行时语义。
例如:
| 语言 | 运行时 |
|---|---|
| Swift | ARC 内存管理 + 并发运行时 |
| Rust | 极简运行时 |
| Zig | 几乎没有运行时 |
Zig 有意将运行时保持得极小。
Zig 作为完整工具链
Zig 最有雄心的目标之一,是简化碎片化的系统构建生态。
传统 C/C++ 开发往往需要多个工具:
compiler → gcc / clang
linker → ld / lld
build system → make / cmake
package mgr → vcpkg / conan
cross toolchains → custom installations
Zig 试图把其中大部分收拢到单个可执行文件中:
zig
命令示例:
zig build
zig build-exe main.zig
zig cc main.c
Zig 编译器同时充当:
- 编译器
- 链接器驱动
- 构建系统
- 交叉编译管理器
内置交叉编译
Zig 最实用的特性之一,是轻松的交叉编译。
Zig 发行包包含:
- LLVM
- Clang
- libc 变体
- 各平台 sysroot
因此,开发者可以在同一台机器上为多个平台编译。
例如:
zig build-exe main.zig -target x86_64-linux
zig build-exe main.zig -target aarch64-macos
zig build-exe main.zig -target x86_64-windows
无需另行安装工具链或 SDK。
单凭这一点,许多开发者即使不写 Zig 代码,也会仅把 Zig 当作编译器驱动来使用。
用 Zig 编译 C
Zig 可以直接编译 C 程序。
例如:
zig cc hello.c
内部流程大致是:
Zig compiler driver
↓
Clang frontend (parsing C)
↓
LLVM backend
Zig 内嵌 Clang,并自动管理交叉编译工具链。
因此,许多项目将 Zig 用作 gcc/clang 工具链的便携替代方案。
不过,Zig 不能编译 Swift 等任意语言,因为它没有包含这些语言的前端或运行时。
Zig 与 Swift:编译与运行时
Zig 和 Swift 都使用 LLVM,但设计差异很大。
| 特性 | Zig | Swift |
|---|---|---|
| 编译器后端 | LLVM | LLVM |
| 运行时 | 极简 | 较为庞大的运行时 |
| 内存模型 | 手动 | ARC |
| 并发 | std.Io(见下文) | 结构化并发 |
| 任务调度 | 用户定义或 std.Io 实现 | 由运行时管理 |
Swift 提供高层运行时,包括:
- 结构化并发
- actor
- 任务调度
- 自动引用计数
Zig 有意避免把这些抽象内置进语言本身。
Zig 中的并发
线程
Zig 提供对操作系统线程的直接访问:
const std = @import("std");
fn worker() void {
std.debug.print("hello from thread\n", .{});
}
pub fn main() !void {
var thread = try std.Thread.spawn(.{}, worker, .{});
thread.join();
}
在 Linux/macOS 上,它直接映射到 POSIX 线程;在 Windows 上则映射到 Windows 线程。
异步 I/O:新模型
Zig 的异步方案经历了很大变化。旧的基于协程的 async/await 语法于 2024 年从语言中移除;新模型 std.Io 在 2025 年末落地,计划进入 Zig 0.16.0。
这个新设计值得了解,因为它很好地体现了 Zig 的理念。
核心想法是把 I/O 抽象到 std.Io 接口背后,就像内存分配被抽象到 std.mem.Allocator 背后一样。在 main() 中设置一次 I/O 实现,再把它传递到整个应用:
var threaded: std.Io.Threaded = .init(gpa);
defer threaded.deinit();
const io = threaded.io();
随后这样表达异步工作:
var a = io.async(doWork, .{ io, "task a" });
var b = io.async(doWork, .{ io, "task b" });
a.await(io);
b.await(io);
io.async 将函数的调用与返回分离。新模型也明确区分了异步和并发:
io.async:表达工作可以重叠,但不保证并行执行io.concurrent:明确请求并发执行,可能返回error.ConcurrencyUnavailable
这个区别很重要。在单线程 I/O 后端上,如果异步任务预期能与另一个任务并行,就可能死锁;io.concurrent 将这一要求明确表达出来,并允许它失败。
取消也是一等机制,设计上能与 Zig 的 defer 自然配合:
var a = io.async(doWork, .{ gpa, io, "task a" });
defer a.cancel(io) catch {};
这意味着,如果错误导致提前返回,尚未完成的任务会自动清理。
std.Io 接口仍在演进,IoUring 和 KQueue 后端还在开发中,但设计方向已经明确:Zig 的异步 I/O 保持显式且可组合,调度器由应用层选择,而不是固定在语言运行时里。
为什么系统工程师偏爱这种模型
高性能系统通常需要严格控制调度。
例如:
- 数据库
- 网络服务器
- 游戏引擎
- 交易系统
这些系统常采用这样的架构:
N CPU cores
↓
N worker threads
↓
each thread owns a work queue
目标是避免:
- 锁
- 跨线程通信
- 不可预测的调度
通用运行时可能自动在线程之间迁移任务,破坏性能保证。Zig 的 std.Io 模型让开发者明确选择和配置 I/O 实现,进而为自身系统定制调度策略,不必绕着运行时的假设工作。
用 Zig 构建的真实系统
一些实际使用 Zig 编写的项目包括:
- Bun JavaScript 运行时
- Ghostty 终端
- TigerBeetle 分布式数据库
这些系统受益于:
- 可预测的性能
- 显式调度
- 对内存的精细控制
为什么 Zig 受 CLI 工具开发者欢迎
Zig 在命令行工具领域也越来越受欢迎。有几个特点让它很适合 CLI 开发。
小体积二进制
Zig 能生成非常小的静态二进制。粗略来说,Zig 二进制通常只有几 MB,往往比功能相当的 Go 或 Rust 二进制更小,不过实际大小会随构建设置和包含的内容而显著变化。
启动快
由于 Zig:
- 没有垃圾回收器
- 运行时极简
- 初始化工作极少
程序几乎能立即启动,非常适合运行时间短的 CLI 工具。
易于分发
Zig 很容易生成单个静态二进制:
mytool-linux
mytool-macos
mytool-windows.exe
不需要外部依赖。
编译时执行(comptime)
Zig 有一个强大特性叫 comptime:程序的一部分可以在编译期间运行。
fn add(comptime T: type, a: T, b: T) T {
return a + b;
}
它支持泛型、编译时校验、代码生成和反射。但 comptime 值得关注的,不只是它能做什么,更是它如何做到。
C++ 等语言通过模板实现类似结果,那是一套独立的编译时小语言,语法以复杂著称。Rust 使用过程宏,本质上是独立程序。Zig 的 comptime 用一个机制取代了这些:在编译时运行普通 Zig 代码。无需另学语法,没有宏系统,也没有模板特化规则。运行时代码和编译时代码,用的是同一门语言。
这是 Zig 对语言设计最有新意的贡献之一。
Zig 作为现代系统工具链
Zig 最具雄心的部分,不只是语言本身,而是试图让整个系统编程工具链现代化。
传统 C 生态积累了几十年的复杂性:
gcc
make
autotools
cmake
pkg-config
custom cross compilers
Zig 试图将它们简化为单个统一工具:
zig
最后的想法
在编程语言版图中,Zig 处于一个有趣的位置:
Swift → application systems language
Rust → safe systems language
Zig → modern C replacement
它没有直接与高层语言竞争,而是专注于改善系统编程的基础:更简单的工具链、显式控制、可复现构建和便携编译。
这门语言仍在成熟,仅异步 I/O 就已经历多轮设计迭代,但方向始终一致。它最终能否取代 C,仍有待观察;不过,它已经在改变开发者思考系统编程工具链的方式。