0.15.1
2025/8/30,0.15.1 发布,自上一个版本来历时 5 个月,共有 162 位贡献者参与,进行了 647 次提交!
本次更新,Zig 团队直接从
0.14.1跨越到了0.15.1。
Zig 目前默认使用 x86 后端,调试编译速度提高了 5 倍;aarch64 后端正在开发中。
目前 Zig 已经进入了语言稳定的最后关头,本次更新带来了部分 break change,同时一直等待的 async 特性也有苗头了。
系统最低版本要求
| 操作系统(Operating System) | 最低版本要求(Minimum Version) |
|---|---|
| Dragonfly BSD | 6.0 |
| FreeBSD | 14.0 |
| Linux | 5.10 |
| NetBSD | 10.1 |
| OpenBSD | 7.6 |
| macOS | 13.0 |
| Solaris | 11 |
| Windows | 10 |
语言变动
小改动:
packed union 字段现在不允许再单独指定 align 属性,这与 packed struct 的现有行为保持一致。此前即使为字段强制指定了对齐方式,也不会实际影响字段的对齐,这次迁移只需删去该部分即可。#22997
移除 async 和 await 关键字
async 和 await 关键字已被移除,@frameSize 也已删除。
虽然 suspend、resume 及协程底层机制是否保留还需依据“无栈协程原语提案”进一步决定,但可以确认,Zig 语言层面将不再有 async/await 这样的关键字。
未来异步相关能力将仅以标准库的一部分(比如 Io 接口)存在。
非穷尽枚举的 switch 改进
现在,针对非穷尽(non-exhaustive)枚举使用 switch 时,可以将显式枚举标签与 _ 分支(代表所有未命名值)组合:
switch (enum_val) {
.special_case_1 => foo(),
.special_case_2 => bar(),
_, .special_case_3 => baz(),
}此外,switch 语句现在也允许同时使用 else 和 _:
const Enum = enum(u32) {
A = 1,
B = 2,
C = 44,
_
};
fn someOtherFunction(value: Enum) void {
// 这样写会编译报错:“error: else and '_' prong in switch expression”
switch (value) {
.A => {},
.C => {},
else => {}, // 此处处理已命名但未列出的标签(这里就是 .B)
_ => {}, // 此处处理未命名标签
}
}布尔向量支持更多运算符
布尔向量现在支持按位非、按位与、按位或、按位异或,以及布尔非等运算。
允许 @ptrCast 从单项指针转换为切片
这是对 0.14.0 版本中 @ptrCast 支持切片长度转换特性的扩展。现在它还可以将单项指针转换为任意切片,返回一个引用与原始指针字节数相同的切片。
const std = @import("std");
test "value to byte slice with @ptrCast" {
const val: u32 = 1;
const bytes: []const u8 = @ptrCast(&val);
switch (@import("builtin").target.cpu.arch.endian()) {
.little => try std.testing.expect(std.mem.eql(u8, bytes, "\x01\x00\x00\x00")),
.big => try std.testing.expect(std.mem.eql(u8, bytes, "\x00\x00\x00\x01")),
}
}$ zig test ptrcast-single.zig
1/1 ptrcast-single.test.value to byte slice with @ptrCast...OK
All 1 tests passed.注意,未来计划将此能力从 @ptrCast 移至新的 @memCast 内建函数,后者在设计上更安全,有助于避免意外越界访问。详情请见 issue #23935。
undefined 上的算术操作新规则
Zig 0.15.x 开始规范 undefined 在不同场景下的行为,特别是在参与算术运算时的规则。简言之,只有那些永远不会导致非法行为的运算符才允许 undefined 作为操作数。其它情况,若操作数为 undefined,将触发非法行为(运行时报错)或编译时报错。
通用的最佳实践是:始终避免对 undefined 进行任何操作。这样一来,这一语言变更(及未来相关变动)基本不会影响你的代码。如果你受到了此项语言变更影响,你可能会在原本可以编译的代码上见到类似的报错:
const a: u32 = 0;
const b: u32 = undefined;
test "arithmetic on undefined" {
// 此处加法现在会报错
_ = a + b;
// 解决方式就是直接避免该操作!
}$ zig test arith-on-undefined.zig
src/download/0.15.1/release-notes/arith-on-undefined.zig:6:13: error: use of undefined value here causes illegal behavior
_ = a + b;
^整数到浮点的损失性转换会导致编译报错
这类报错本就预期存在,只是直到现在才实现。若某整数值在 comptime 被强制转换为浮点类型,但该整数无法被该浮点数精确表示,则编译器现在会报错。例如:
test "big float literal" {
const val: f32 = 123_456_789;
_ = val;
}$ zig test lossy_int_to_float_coercion.zig
src/download/0.15.1/release-notes/lossy_int_to_float_coercion.zig:2:22: error: type 'f32' cannot represent integer value '123456789'
const val: f32 = 123_456_789;
^~~~~~~~~~~通常的解决办法是将整数字面量改为浮点字面量,以此显式加入浮点数的舍入规则:
test "big float literal" {
const val: f32 = 123_456_789.0;
_ = val;
}$ zig test lossy_int_to_float_coercion_new.zig
1/1 lossy_int_to_float_coercion_new.test.big float literal...OK
All 1 tests passed.构建系统
未归类的变更:
- zig build: 在构建总结前输出一个换行
macOS 文件系统监听
现在,zig build 的 --watch 参数已支持 macOS 系统。在 Zig 0.14.0 时,这个参数虽然可用,但对大多数编辑器表现异常;而在 Zig 0.15.x 里,这一功能已被重新实现,采用了 macOS 的 File System Events API,确保文件系统变更监听快速且可靠。
所以,如果此前因 macOS 问题没有使用 --watch,现在可以放心使用了。尤其是你想试用 增量编译 时,推荐为 zig build 传递 --watch -fincremental。
Web 界面与时间报告
Zig 0.14.0 提供了用于内置模糊测试 fuzzer 的实验性 Web 界面。在 0.15.x 中,这一界面被扩展为更通用的构建系统 Web 界面。可通过 zig build --webui 选项启用。启用后,zig build 进程会在构建完成后持续运行。
Web 界面本身主要显示所有构建步骤及其状态,同时有按钮可手动触发重新构建(所以可以作为 zig build --watch 流程的替代方式)。如果使用 --fuzz,则会暴露 Fuzzer 相关界面,其内容与 0.14.0 基本一致。
此外,Web 界面新增了“时间报告”功能。只需为 zig build 传递 --time-report,即可在 Web 界面上展开查看构建图中各步骤的耗时信息。尤其是每个 std.Build.Step.Compile,都会有详细的子阶段统计:Zig 编译器各部分的快慢情况,以及哪些文件/声明在语义分析、生成机器码、链接阶段消耗的时间最多。

这是一项较高级别的功能,非常适合定位导致编译变慢的代码片段——只需展开 "Declarations" 表格,查看最耗时的前几项。

如果本次编译用到了 LLVM 后端,还会额外提供 LLVM pass 分阶段的耗时信息。
Compiler
x86 后端
Zig 0.15.x 版本在 Debug 模式下默认启用了 Zig 自实现(self-hosted)的 x86_64 代码生成后端。
更具体来说,现在只要目标架构是 x86_64 并且使用 Debug 模式,默认都会启用该后端(除了 NetBSD、OpenBSD 和 Windows,这几个平台目前因为 链接器 存在缺陷,仍然默认使用 LLVM 后端)。
启用自实现 x86_64 后端后,你将可以直接感受到 Zig 项目过去几年投入的成果:编译速度显著提升——大多数场景下比 LLVM 快了大约 5 倍。而这还只是开始;自实现 x86_64 后端是专为 增量编译 而设计的,这项功能足够稳定时,预计还会有极大加速。极致的编译速度一直是 Zig 项目的核心目标之一,我们已经默默推进多年,这次发布是阶段性成果的集中体现。
使用自实现 x86 后端,还能避免受上游 LLVM Bug 的影响(目前我们正在跟踪 60 多个相关 Bug)。事实上,自实现 x86 后端在我们的“行为测试集”上,已能通过比 LLVM 后端更多的用例(1984/2008,相比 LLVM 的 1977/2008)。换句话说,该后端对 Zig 语言的实现更为完整和准确。
当然,目前自实现 x86 后端本身也还存在部分缺陷和 Bug。如果你遇到了相关问题,可以通过命令行参数 -fllvm,或在创建 std.Build.Step.Compile 时设置 .use_llvm = true,将 Debug 编译切换回 LLVM 后端。此外,当前自实现 x86 后端生成的机器码在性能上略慢于 LLVM 后端。
尽管如此,在绝大多数开发场景中,自研后端已经是更优秀的选择。比如 Zig 核心开发团队已经在很长一段时间内,主要用自研 x86 后端编译 Zig 编译器,极大提升了开发效率,现在 Zig 编译器只需几秒即可构建完成,而以往用 LLVM 则要 1-2 分钟。你也可以期待在自己项目的开发体验中获得类似提升。
aarch64 后端
在自实现 x86 后端 已经足够成熟并启用为默认后,Jacob 将目标转向了新的架构:aarch64。该架构近年来越来越受欢迎,尤其是现代苹果电脑都基于它。因此,aarch64 成为了 Zig 项目在摆脱 LLVM 依赖的自研代码生成后端中的下一个重点。
这个后端目前还在开发早期阶段,Jacob 已经能基于 x86 后端的经验,尝试全新的设计思路。虽然现在下结论还为时过早,但我们预计新设计将进一步提升编译器性能(甚至有望超越自实现 x86_64 后端),并提升输出机器码的质量,最终目标是在 Debug 模式下与 LLVM 的代码生成质量一较高下。你可以在这篇 开发日志 里看到更多细节。
目前该后端已通过了 1656/1972(84%)项与 LLVM 行为一致性的测试,因此还未准备好作为默认后端启用,在实际项目中也暂时无法使用。但它正在快速进步,预计将在未来版本成为 Debug 模式下的默认选择。
我们自实现代码生成后端的工作,是 Zig 长期计划的一部分,未来将 使 LLVM 成为可选依赖,并从编译器实现中解耦。实现这一目标将显著提升编译速度,为 Debug 构建带来优秀的增量编译支持,甚至有可能探索 LLVM 无法高效支持的新语言特性。
增量编译(Incremental Compilation)
Zig 0.15.x 在正在开发中的增量编译(Incremental Compilation)功能上又取得了进展。该功能允许编译器只重新编译修改过的代码,从而极大提升二次编译的速度。包括文件导入变更相关的各种 Bug 在此版本中均有所修复。
请注意,这依然属于实验性功能——目前仍有已知 Bug,可能导致错误编译或错误的编译报错。但现在已经足够稳定,可以与 -fno-emit-bin 结合起来可靠使用。**如果你有一个编译时间很长的庞大项目,非常建议结合使用 --watch、-fincremental 和 -Dno-bin 来改善编译反馈体验。**如果你不清楚如何在构建脚本里暴露 -Dno-bin,可以寻求社区帮助。
接下来的发布周期还会继续努力,目标是将增量编译默认开启。如果你有兴趣尝鲜,可以参考 #21165 了解详情。
多线程代码生成(Threaded Codegen)
Zig 编译器自设计之初就考虑到了并行化。通过让编译的不同阶段在多个线程间并行运行,显著提升了编译性能。Zig 早期还是主要单线程的,但从 0.14.0 版本起,部分底层代码生成后端已经能够与前端(语义分析阶段)并发执行。Zig 0.15.x 在此基础上更进一步,实现了语义分析、代码生成、链接这几个阶段可完全并行,且代码生成本身还能进一步拆分到多个线程中去。
与 0.14.0 相比,开启自实现后端(如 x86 后端)后,这一改进通常会带来一次明显的编译性能提升。具体提升幅度与你编译的代码结构有关,有时变化不大,有时提升可高达 50%。举个实际例子:在某台机器上,使用自实现 x86_64 后端编译 Zig 编译器,耗时从 13.8 秒降到了 10.0 秒,提升了 27%。
这份开发日志 有更深入的技术细节。总之,得益于各阶段并行执行,当你使用自研后端时,编译速度会得到显著提升。而且现在终端会显示更详细的进度信息。
支持在模块级别配置 UBSan 模式
现在 Zig CLI 和构建系统允许更灵活地控制 C 兼容未定义行为检测(UBSan)模式。zig build-exe 及相关命令现支持 -fsanitize-c=trap 和 -fsanitize-c=full,其中旧的 -fsanitize-c 就等价于 -fsanitize-c=full。
- 选择
full时,UBSan 运行时会被编译并链接进你的程序,遇到未定义行为时提供更详尽的错误信息,但相应代码体积会略大。 - 选择
trap时,会插入陷阱指令,触发未定义行为时进程会收到SIGILL,但是代码体积更小。 如未显式指定,默认模式由构建模式决定。
对于 zig cc,在已有的 -fsanitize=undefined 外,现在也能理解 -fsanitize-trap=undefined,与 zig build-exe 上的 -fsanitize-c=trap 基本等价。
因为本次变更,std.Build API 里的 sanitize_c 字段类型从 ?bool 替换成了 ?std.zig.SanitizeC。如果你过去设置为 true/false,现在应分别切换为 .full 或 .off,以保持原有行为。
测试编译为对象文件(Compile Tests to Object File)
通常,Zig 的测试功能会直接构建一个可执行文件。但有些场景下,你可能需要只生成测试用的对象文件而非最终可执行文件,比如让外部代码以共享库方式加载你的应用。Zig 0.15.x 针对这些需求,允许测试生成对象文件,便于后续以你希望的方式进行链接。
命令行下,可通过运行 zig test-obj(而不是 zig test)实现。
使用构建系统时,可通过新版 std.Build API,在调用 std.Build.addTest 时传递 emit_object 选项,这样返回的 Step.Compile 会生成对象文件。这个对象文件和其他对象一样,可以被安装用于外部使用,或直接链接到其他 build 步骤。不过注意:启用此功能后,build runner 与 test runner 不会直接通信,退回到默认的 zig test 方式(即用 stderr 报告测试失败)。所以如果你用到这个特性,可能还需自定义 test runner,让它能与外部测试框架协作。
Zig Init
zig init 命令在本版本中配备了新版项目模板。
旧模板包含用于生成 Zig 模块静态库的代码,这容易让初学者误以为“生成静态库”是 Zig 代码复用的首选方式。
新的项目模板则同时提供了 Zig 模块与可执行文件的样板代码。这符合大多数开发需求,同时也展示了如何将可复用逻辑拆分到模块中、并在应用中进行调用。如果你只需生成单一类型的产物,可以直接删除不需要的部分。而保留这些内容也能温和地提醒你:
- 为你的库设计配套工具
- 在你的可执行文件中方便地访问可复用逻辑
现在你可以通过在 zig init 命令后加上 --minimal 或 -m 参数,生成极简模板。执行该命令会创建 build.zig.zon 文件,并在不存在时,创建仅包含 build 函数框架的 build.zig 文件。这个选项适用于已经熟悉 Zig 构建系统且主要希望方便生成带有正确指纹的 Zon 文件的用户。
链接器(Linker)
在本次发布周期中,Zig 的链接器仅进行了部分 Bug 修复与维护。但请注意,链接器将在下一个发布周期成为重点优化对象,目标是进一步改进增量编译(Incremental Compilation)相关体验。
Fuzzer(模糊测试器)
尽管核心团队一直对模糊测试保持极大热情,但在本发布周期内,团队成员未能投入足够精力推动 fuzzer 的进一步发展。我们在此感谢贡献者 Kendall Condon,他提交了一个 pull request 大幅提升 fuzzer 能力,目前正在耐心等待核心团队的后续协作。
Bug 修复
点此查看本次发布周期内关闭的 201 个 bug 报告的完整列表。
在本次发布周期内,新的 bug 持续被发现和修复。为了简洁起见,绝大多数 bug 修复未在本发行说明中详细列出。
本版本仍然存在已知缺陷
即使使用 Zig 0.15.x,在较复杂的项目中工作,也可能需要你主动参与到开发流程当中,一起反馈和解决问题。
当 Zig 进入 1.0.0 正式版后,Tier 1 支持将会增加专门的 bug 管理政策作为强制要求。
工具链(Toolchain)
LLVM 20
本次 Zig 升级到了 LLVM 20.1.8。此升级涵盖 Clang(zig cc/zig c++)、libc++、libc++abi、libunwind 以及 libtsan。
Zig 现已支持使用 LLVM 的 SPIR-V 后端(backend)。请注意,自托管的 SPIR-V 后端仍是默认选项。如需使用 LLVM 后端,可通过 -fllvm 参数进行构建。
交叉编译时支持 FreeBSD 动态链接 libc
Zig 现在通过为动态链接的 libc 提供桩库(stub libraries),允许交叉编译到 FreeBSD 14+,这与 glibc 的交叉编译方式类似。此外,还会一并提供所有系统和 libc 头文件。
交叉编译时支持 NetBSD 动态链接 libc
Zig 现在通过为动态链接的 libc 提供桩库,支持交叉编译到 NetBSD 10.1+,方式同 glibc 类似。同时也会提供所有系统及 libc 头文件。
glibc 2.42
交叉编译时现已可用 glibc 2.42 版本。
允许静态链接本地 glibc
Zig 现在允许静态链接本地 glibc。尽管这通常不是一个好主意,但对于某些不依赖 glibc 动态特性的特殊场景(如 NSS、iconv 等内部依赖动态链接的功能未被使用时),可以这样做。
需要注意,若使用 Zig 内置的 glibc 进行交叉编译,该 glibc 仅以动态库形式提供,因此不适用静态链接。
MinGW-w64
本版本将内置的 MinGW-w64 升级到提交号 38c8142f660b6ba11e7c408f2de1e9f8bfaf839e。
zig libc
本次发布开始尝试在 Zig 提供的静态链接 libc(目前包括 musl、wasi-libc 和 MinGW-w64)之间共享代码。我们在新的 zig libc 库中用 Zig 代码重新实现了这些 libc 的通用函数。这意味着每个函数都将有唯一的权威实现,今后无需再分别修改上述项目的第三方 libc 代码即可改进实现。我们的长期目标是彻底摆脱对这些 libc 上游 C 实现代码的依赖,仅保留其头文件(headers),但这还需要很多工作。
这个工作非常欢迎社区贡献。如果你对此感兴趣,可查看 issue #2879 参与。
zig cc
zig cc 现在能正确识别 -static 和 -dynamic 标志。最值得注意的是,这允许静态链接本地 glibc,以及动态链接交叉编译的 musl。
zig objcopy 功能回退
很抱歉,相关代码未达到质量标准,需重新设计。一部分功能仍可使用,另外一些功能会报“unimplemented”(未实现)的错误。详见 #24522。
路线图(Roadmap)
0.16.0 版本周期的两大核心主题将是异步 I/O 和 aarch64 后端。
下一步要达成的重要里程碑包括:
- 引入 I/O 作为 Interface(接口类型)
- 使 aarch64 后端成为 debug 模式下的默认后端
- 改进链接器实现,消除对 LLD 的依赖,并支持增量编译
- 加强内置 Fuzzer,使其具备与 AFL 及其它先进模糊测试工具竞争的能力
I/O 作为接口
未来,Zig 将把全部文件系统、网络、定时器、同步机制,以及任何可能阻塞(block)的内容,都重构为全新的 std.Io 接口。所有涉及 I/O 的代码都需要接收一个 Io 实例,类似于所有需要内存分配的代码都需要一个 Allocator 实例。
这将让你可以编写对应用并发模型无感知的、可复用且高性能的包(package),支持异步机制,发现更多类型的 bug,并让事件循环(event loop)成为 Zig 生态中的“一等公民”。