0.17.0
2026/10/2,0.17.0 发布,历时 5 个月,有 206 位贡献者参与,一共进行了 925 次提交!
如果要用一句话概括这个版本,那就是:0.17.0 重做了构建系统,并继续为语言“收口”。
这个版本原本被规划为一个短周期版本,但最终的工作量相当可观:构建系统被拆分为“配置进程”和“执行进程”,并引入了面向 IDE 等第三方工具的 Build Server Protocol;新 ELF linker 的能力大幅增强,使得绝大多数面向 x86_64-linux 的项目都能用上增量编译。同时,语言层面继续清理历史设计:数组乘法 **、errdefer 捕获、void{}、i0 被移除,@cImport 也在经历了 0.16.0 的迁移期后被正式移除。
具体的迁移方式请参阅 0.17.0 升级指南。
目标支持
0.17.0 在目标支持上继续稳步推进,比较值得注意的点有:
aarch64-openbsd现在会在 Zig 的 CI 中原生测试;aarch64-freebsd和aarch64-netbsd的 CI 任务除了master推送外,现在也会在 PR 上运行- 绕过了一个导致大多数
aarch64-windows二进制(包括 Zig 编译器本身)无法正常工作的 LLVM bug - 面向
aarch64-openbsd时,编译器会强制启用该平台要求的代码加固手段,确保生成的二进制能够真正运行 - 32 位 ARM 与 SPARC 现在在崩溃和断言失败时也能输出栈回溯(Thumb-only 目标仍有少量工作待完成);在 AArch64 上进行栈展开时,现在可以正确处理指针认证(pointer authentication)指令
- 新增
loongarch32-linux-gnu[sf]目标支持 - 64 位 SPARC,尤其是
sparc64-linux,现在已经基本可用——这主要得益于新 ELF linker 对该目标的支持已经优于 LLD - 标准库已移植到 x86-64 上的 x32 ABI 与 64 位 MIPS 上的 N32 ABI。这类 ILP32 ABI 允许使用 64 位指令集,但指针只有 32 位,用可用地址空间换取更低的内存占用与更好的缓存利用率
- 新增了一些游戏主机的目标信息:
aarch64-switch、arm-gba、mipsel-psx、powerpc-wiiu - 新增非常早期的
xtensa-linux支持,目前只能通过 C 后端或实验性的 LLVM 后端使用 - 使用 C 后端时,标准库现已支持
arc[eb]-linux、csky-linux与m88k-openbsd;不使用 libc 时,标准库现已支持microblaze[el]-linux、sh[eb]-linux与sparc-linux - 所有 PowerPC 目标现在强制使用
-mabi=ieeelongdouble。这只是把既成事实正式化:Zig 从未支持过 IBM 的“double-double”long double格式,将来大概率也不会支持。因此本版本移除了powerpc-linux-gnueabi[hf](glibc 在该目标上只支持 double-double),powerpc-linux-musleabi[hf]仍然受支持 - 本版本移除了
powerpc64-linux-gnu:Zig 只支持为 64 位 PowerPC 链接 ELFv2 二进制,而 glibc 并未在大端上正式支持 ELFv2,也不支持 IEEElong double - 几乎所有架构、所有受支持操作系统上的本机 CPU 型号与特性检测都得到了大幅增强
- 在目标查询语法中,只有当三元组确实使用本机 libc(即省略了 ABI 部分)时,才会进行本机 libc 版本检测,这更符合大家对目标查询的直觉
部分目标的基线 CPU 型号也发生了变化:
| 目标 | 新的基线 CPU 型号 |
|---|---|
aarch64-haiku | cortex_a55 |
m68k-* | M68030 |
mips64-openbsd | octeon |
powerpc-netbsd | 750 |
powerpc64-freebsd | pwr8 |
powerpc64-linux | pwr8 |
powerpc64-openbsd | pwr9 |
s390x-* | arch11 |
sparc-* | generic |
sparc-linux | v9 |
sparc64-* | ultrasparc |
xtensa-* | esp32 |
Tier 系统
Zig 依旧把对各目标的支持程度划分为四档(Tier 1 最高)。与上个版本相比,Tier 1 新增了“内置 fuzzer 可以在该目标上工作(如适用)”这一要求:
- Tier 1:所有非实验性语言特性都已知能正常工作;编译器可以不依赖 LLVM 直接为该目标生成机器码;内置 fuzzer 可以在该目标上工作(如适用)
- Tier 2:标准库的跨平台抽象覆盖了该目标;断言失败和崩溃时可以输出栈回溯;交叉编译时可获得 libc(如适用);CI 在每次推送时都会构建该目标的模块测试
- Tier 3:编译器可借助 LLVM 等外部后端为该目标生成机器码;链接器可以生成该目标的对象文件、库与可执行文件
- Tier 4:编译器只能为该目标生成汇编或 C 源码
目前唯一的 Tier 1 目标仍然是 x86_64-linux。
本版本的支持表中还引入了“过时(obsolescent)”标记:编译器和标准库会尽力维持对这些目标的支持,但这些支持预计最终会被移除。被标记的目标有 x86-windows、x86_64-macos、x86_64-maccatalyst、thumb-windows、x86-freebsd 与 x86-illumos。
其他附加目标
除了 Tier 1–4 这套划分之外,Zig 对下面这些目标也有不同程度的支持,但 tier 系统本身并不完全适用:
aarch64-driverkit、aarch64[_be]-freestanding、aarch64-fuchsia、aarch64-hurd、aarch64-switch、aarch64-uefi、alpha-freestanding、amdgcn-amdhsa、amdgcn-amdpal、amdgcn-mesa3d、arc[eb]-freestanding、arm[eb]-freestanding、arm-3ds、arm-fuchsia、arm-gba、arm-uefi、arm-vita、avr-freestanding、bpf(eb,el)-freestanding、csky-freestanding、ez80-freestanding、ez80-tios、hexagon-freestanding、hppa[64]-freestanding、kalimba-freestanding、kvx-freestanding、lanai-freestanding、loongarch(32,64)-freestanding、loongarch(32,64)-uefi、m68k-freestanding、m88k-freestanding、microblaze[el]-freestanding、mips[64][el]-freestanding、mipsel-psx、mipsel-psp、msp430-freestanding、nvptx[64]-cuda、nvptx[64]-nvcl、or1k-freestanding、powerpc-wiiu、powerpc[64][le]-freestanding、powerpc64-ps3、propeller-freestanding、riscv(32,64)[be]-freestanding、riscv(32,64)-uefi、riscv64-fuchsia、riscv64-hurd、s390x-freestanding、sh[eb]-freestanding、sparc[64]-freestanding、spirv(32,64)-opencl、spirv(32,64)-opengl、spirv(32,64)-vulkan、spork8-freestanding、thumb[eb]-freestanding、thumb-fuchsia、thumb-gba、thumb-vita、ve-freestanding、wasm(32,64)-emscripten、wasm(32,64)-freestanding、x86[_16,_64]-freestanding、x86[_64]-hurd、x86[_64]-uefi、x86_64-driverkit、x86_64-fuchsia、x86_64-plan9、x86_64-ps4、x86_64-ps5、xcore-freestanding、xtensa[eb]-freestanding。
和上个版本相比,这份名单里新增了 Fuchsia、Hurd、Plan 9、PS4 / PS5、Switch、GBA、PSX、Wii U 以及 eZ80 等一批目标。
系统最低版本要求
标准库对部分操作系统有最低版本要求,这同样会影响 Zig 编译器本身。和 0.16.0 相比,macOS 的最低版本从 13.0 提升到了 15.0,DragonFly BSD 从 6.0 提升到了 6.4:
| 操作系统(Operating System) | 最低版本要求(Minimum Version) |
|---|---|
| DragonFly BSD | 6.4 |
| FreeBSD | 14.0 |
| Linux | 5.10 |
| NetBSD | 10.1 |
| OpenBSD | 7.8 |
| macOS | 15.0 |
| Windows | 10 |
语言变动
语言稳定性进展
自 0.16.0 发布以来,Zig 在语言稳定化方面取得了很大进展,这是通往 1.0 的路线图中的关键一步。
在这一个版本周期内,核心团队讨论并决定了大量语言提案:接受了约 25 个,拒绝了约 125 个。截至发布时,Codeberg 上还有 23 个、旧的 GitHub issue 跟踪器上还有 61 个尚未决定的语言提案。虽然仍有一些重大决定有待做出,但这意味着语言设计正在明显地走向定型。
@bitCast 的语义被重新定义
0.17.0 修改了 @bitCast 的定义。整数与整数之间、整数与 packed struct / packed union 之间的转换不受影响,但涉及数组或向量类型的 @bitCast 语义发生了变化,而且这种变化可能在不触发编译错误的情况下改变程序行为,升级时建议逐一审查涉及数组或向量的 @bitCast。
新的定义是:@bitCast 会把一个值的逻辑位表示重新解释为另一种类型。拥有逻辑位表示的类型包括 void、bool、整数(comptime_int 除外)、浮点数(comptime_float 除外)、以整数为底层类型的 enum(T) / packed struct(T) / packed union(T),以及由这些类型组成的数组或向量。
对整数和浮点数而言,逻辑位表示从最低有效位开始、到最高有效位结束;对数组和向量而言,则按照从第一个元素开始的顺序,把所有元素的逻辑位表示依次拼接起来。
这意味着新的 @bitCast 在小端目标上与旧行为基本一致,但它与目标的端序完全无关——在大端目标上,结果会与以前不同。
此外,@bitCast 不再允许用于 extern struct 和 extern union。这类代码通常是想重新解释值在内存中的表示(也就是常说的“type punning”),此时应改用 @ptrCast 或 extern union。
语法被形式化并进行了模糊测试
Zig 的形式化语法 grammar.peg 与实际的语言实现一直存在不少出入,形式化语法可能从未与手写的 tokenizer 和 parser 完全吻合过。
这个问题现在被修复了:官方编写了一个工具,以 grammar.peg 为输入生成一个简单的递归下降解析器,再把它作为“预言机(oracle)”对手写的 std.zig.Ast.parse() 做模糊测试。这保证了语法只有单一的事实来源,也为后续的语法调整和语言规范工作扫清了障碍。
C 翻译迁移到外部包
@cImport 在 0.16.0 中被标记为 deprecated,并在 0.17.0 中被正式移除。
同时,构建系统内置的 std.Build.Step.TranslateC(即 b.addTranslateC)也被标记为 deprecated,官方推荐改为显式依赖 ZSF 官方维护的 translate-c 包。它与内置构建步骤是同一套实现,但为翻译结果提供了更多配置项,并且拥有独立于 Zig 工具链的发布节奏。
新增 @backingInt 与 @fromBackingInt
新的 @backingInt 和 @fromBackingInt 内建函数取代了已被标记为 deprecated 的 @intFromEnum 与 @enumFromInt,并且 zig fmt 会自动完成这一升级。
@backingInt适用于所有枚举,以及显式指定了底层整数类型的 bitpack(packed struct/packed union);对于带标记的联合类型,它会返回当前激活标记的底层整数@fromBackingInt通过结果位置推断结果类型(任意枚举,或显式指定了底层整数类型的 bitpack),参数必须恰好是该底层整数类型。对于枚举,如果传入undefined或无效的标记值,会触发带安全检查的非法行为
另外,当 @bitCast 的目标类型是枚举时,现在也会对无效的标记值进行安全检查;标准库新增了 std.meta.BackingInt 用于获取 @backingInt 的结果类型;空枚举由于无法实例化,现在要求以 noreturn 作为底层类型。
新增 @SpirvType
SPIR-V 中有许多类型(例如 image、sampler)在 Zig 的类型系统中并没有对应物。过去引用它们的唯一方式是内联汇编,这导致无法把纹理或存储缓冲区声明为普通的全局变量。
0.17.0 新增了 @SpirvType(comptime options: std.lang.Type.Spirv) type,可以创建 .sampler、.image、.sampled_image、.runtime_array 等 SPIR-V 类型。在非 SPIR-V 目标上使用它会产生编译错误。
数组乘法语法被移除
数组乘法语法 a ** b 被移除,取而代之的是 @splat。例如 [1]u8{0} ** n 应改写为 @as([n]u8, @splat(0)),或者在有结果类型时直接写 @splat(0)。
新增 @divCeil
新的 @divCeil 执行向正无穷方向取整的整数除法,补齐了已有的 @divTrunc、@divFloor 和 @divExact:
@divCeil(5, 3) == 2
@divCeil(-5, 3) == -1和其他除法内建函数一样,调用者需要保证除数不为 0 且结果不会溢出。从此不再需要写 std.math.divCeil(a, b) catch unreachable 了。
@hasDecl 只对公开声明返回 true
过去,@hasDecl 对公开声明以及“与调用处位于同一文件中”的声明都会返回 true。现在,它的行为不再取决于调用处所在的文件:只有 pub 声明才会返回 true。
允许解引用编译期已知长度的切片
现在,长度在编译期已知的切片可以直接解引用为数组,或者强制转换为数组指针:
const slice: []const u16 = &.{ 1, 2, 3 };
const array: [3]u16 = slice.*;
const array_ptr: *const [3]u16 = slice;void{} 语法被移除
void{} 不再是合法的语法,请使用 {} 代替。
errdefer 捕获被移除
errdefer |err| { ... } 中的捕获语法不再被允许。如果需要观察具体的错误,可以把函数拆成两层,在外层使用 catch |err| 处理。
i0 被移除
i0 不再是合法的整数类型。这个类型本身没有意义,所以并没有直接的替代品,不过几乎所有用到它的地方都可以透明地替换为 u0。
全局链接属性 internal 与 link_once 被移除
std.lang.GlobalLinkage 中的 internal 和 link_once 被移除了,因为它们语义不清,代码生成和链接方面的支持也不完整。link_once 的用途大多可以用 weak 替代;至于 internal,只需一开始就不要 @export 该符号即可。
标准库
先看一些零散的改进:
@exp与@exp2新增了对f128的支持- 新增
std.Io.Semaphore.waitTimeout - 新增用于图像采样、查询与写入的
std.spirv辅助函数 ArrayHashMap.setKey不再重新计算整个索引std.Target.parseCpuModel现在返回可选值,而不是错误std.debug.Pdb会对内联的源码位置去重- 对
hash.crc命名空间进行了全面审查 std.fs.path为relative和resolve新增了追加(appending)版本(std.fs.path自0.16.0起已被标记为 deprecated,推荐通过std.Io.Dir.path使用)
弃用与移除
std.builtin被标记为 deprecated,改为std.langstd.meta.fieldInfo、std.meta.fieldNames、std.meta.fieldTypes被标记为 deprecated,改为直接使用@typeInfostd.DoublyLinkedList.pop被标记为 deprecated,改为std.DoublyLinkedList.popLaststd.gpu更名为std.spirvstd.heap.memory_pool.AlignedManaged与ExtraManaged被移除,改为std.heap.memory_pool.Aligned与Extrastd.ascii.indexOfIgnoreCase系列被移除,改为std.ascii.findIgnoreCase系列std.bit_set.Integer、std.bit_set.Array、std.enums.EnumSet的initEmpty/initFull被移除,改为empty/fullstd.mem.containsAtLeastScalar2被移除,改为std.mem.containsAtLeastScalarstd.mem.readPackedIntNative/readPackedIntForeign/writePackedIntNative/writePackedIntForeign被移除,改为std.mem.readPackedInt/writePackedInt
SafeAllocator 取代 DebugAllocator
std.heap.DebugAllocator 被一个线程安全的新分配器 std.heap.SafeAllocator 取代,std.heap.DebugAllocator 与 std.heap.Check 均被标记为 deprecated。SafeAllocator 提供以下保证:
deinit会报告所有泄漏,并释放所有后备内存- 所有分配不匹配的情况都会导致 panic 或段错误
- 来自其他
SafeAllocator实例的分配会触发 panic(当Options.canary不同时) - 重复释放以及 resize / remap / free 之间的竞争会导致 panic 或段错误
- 只要后备分配器不复用内存,它自己也不会复用内存,因此大多数“释放后写入”都会导致段错误,或者最终被检测到并 panic
每次分配后面都跟随一个 AllocFooter,其中存放着分配的元数据和栈追踪信息,并通过校验和保护,以便捕获越界写入造成的破坏。官方给出的基准测试显示,在使用该分配器构建标准库测试时,耗时减少了约 25%,峰值内存占用减少了约 50%。
StackFallbackAllocator 被重做
“小向量(small vec)”优化中常用的“栈缓冲区 + 回退到堆分配”的分配器被重新设计。旧设计存在几个问题:无法指定缓冲区的对齐;分配器对缓冲区大小是泛型的;调用 .get() 会改变分配器本身的状态,与其他分配器不一致,因此需要额外的运行时安全检查。
现在,缓冲区像大多数标准库 API 一样由调用者作为参数传入。需要注意的是,发布说明中仍沿用了 StackFallbackAllocator 这个名字,但在 0.17.0 的标准库中,它的实际名称是 std.heap.BufferFirstAllocator,旧的 std.heap.stackFallback 已不复存在。
ArrayList
getLastOrNull被标记为 deprecated,并更名为lastgetLast被标记为 deprecated,请改用last().?- 新增
lastPtr,返回?*T
此外还新增了指针稳定性检查,用于更快地定位 ArrayList 的误用问题。
debug.SafetyLock 支持共享锁
已有的 lock 与 unlock 依旧是独占锁,适用于可能修改数据的场景;新增的 lockShared 与 unlockShared 可用于“多个使用者只读、不修改数据”的场景。
fmt.allocPrint 迁移到 mem.Allocator
std.fmt.allocPrint(gpa, ...) 被标记为 deprecated,现在可以直接在分配器上调用 gpa.print(...);allocPrintSentinel 对应 printSentinel。
格式化打印增强
用于把字符串转义成可以放进双引号字符串字面量的 {q} 说明符放宽了转义规则,UTF-8 编码的数据现在可以原样通过;新增 {qf},用于对某个 format() 的输出进行双引号转义。
std.zon.parse 被重做
std.zon.parse 现在接受结构体参数,并从 arena 中分配结果。部分方法也被改名:fromSliceAlloc 改为 fromSlice,原来的 fromSlice 改为 fromSliceNoAlloc,其他“from”系列方法也按相同规则改名。
另外新增了 updateFromSlice 等“updateFrom”系列方法,它们会用 ZON 源中指定的字段覆盖内存中已有值的对应字段。这在按不同优先级加载配置文件时非常有用,例如文本编辑器同时拥有全局配置和项目级配置的场景。
bit_set 类型改名
为了命名一致,bit_set 中的类型被改名,旧名称以及 managed 版本被标记为 deprecated:
| 旧名称 | 新名称 |
|---|---|
std.bit_set.IntegerBitSet | std.bit_set.Integer |
std.bit_set.ArrayBitSet | std.bit_set.Array |
std.StaticBitSet、std.bit_set.StaticBitSet | std.bit_set.Static |
std.DynamicBitSetUnmanaged、std.bit_set.DynamicBitSetUnmanaged | std.bit_set.Dynamic |
std.DynamicBitSet、std.bit_set.DynamicBitSet | std.bit_set.DynamicManaged(deprecated) |
std.lang.Type 采用“数组结构体”风格
进行类型反射时,结构体、联合等类型的信息现在以**数组结构体(Struct-Of-Arrays)**的形式返回:原来由 StructField 等组成的 fields 数组,被拆分成了 field_names、field_types、field_attrs 等多个并列的切片。这与 0.16.0 引入的 @Struct、@Union 等类型构造内建函数的参数形式保持了一致。
lang.OptimizeMode 更名为 lang.Optimize
std.lang.OptimizeMode 更名为 std.lang.Optimize,并去掉了枚举标签中的“release”字样:Debug → debug、ReleaseSafe → safe、ReleaseFast → fast、ReleaseSmall → small。
虽然功能上没有变化,并且提供了向后兼容的声明,但这依然是一个破坏性变更:使用 == 或 != 与旧名称比较的表达式将无法编译。
同时,推荐使用 std.lang.Optimize.runtimeSafety 代替 std.debug.runtime_safety,因为前者能让调用处感知到自身所在模块的设置,而不是标准库模块的设置。
@import("builtin") 中的冗余常量被弃用
@import("builtin") 中冗余的 cpu、os、abi 和 object_format 被标记为 deprecated,并将在 0.18.0 中移除,请改用 target 常量上对应的字段:target.cpu、target.os、target.abi、target.ofmt。
mem.eql 与 mem.findDiff 正确处理浮点数
std.mem.eql 和 std.mem.findDiff 在两个输入是同一块内存的切片时会走捷径直接返回。但这个捷径只有在该类型的 == 满足自反性时才正确,而浮点数并不满足(例如 nan != nan)。现在处理浮点切片时会禁用这个优化。
Uri 与 net.HostName 解耦
Uri 遵循的 RFC 3986 与 HostName 遵循的 RFC 1123 对“合法主机名”的定义大不相同,因此 Uri 中所有与 HostName 相关的内容都被移除:Uri.getHost 移动为 HostName.fromUri(由于语义差异较大,没有提供平滑的弃用过渡),Uri.getHostAlloc 被直接移除。
构建系统
0.17.0 的构建系统经历了一次大规模重做,先看一组 API 变动:
b.build_root(Directory)改为b.root(Cache.Path)ConfigHeader.Options中的include_guard_override改为include_guardLazyPath.getDisplayName改为format(使用"{f}"打印)LazyPath.basename被移除,因为该值在执行阶段之前是未知的b.findProgram被拆分为findProgram与findProgramLazy,API 也做了面向未来的调整ConfigHeader被修复,现在所有风格都会报告未使用的值Run步骤中带Prefixed的一系列参数方法被合并,统一改为带选项的...Arg2版本,例如addArtifactArg2、addOutputFileArg2、addFileArg2、addDirectoryArg2等
配置进程与执行进程分离
zig build 现在会在一个独立的可执行文件中运行项目的 build.zig(配置进程,configurer),而包管理和构建图的执行则由另一个进程负责(执行进程,maker)。这让 zig build 在多个方面变得更快:
- 修改
build.zig时,maker 可执行文件不会改变,因此安装 Zig 之后只需要构建一次(“首次设置”) - maker 可执行文件以开启优化的方式构建,在引入了
--watch和--fuzz之后,这一点变得越来越有价值 - 根据
zig build使用的命令行参数,有时可以完全跳过build.zig的执行
此外,配置结果现在会被序列化为一种紧凑的二进制格式,可供第三方工具使用,它也是新的 Build Server Protocol 的一部分。过去通过 fork build runner 来满足这类需求的做法不再受支持。可以使用 --print-configuration 以 .zon 格式把配置输出到标准输出。
缓存系统重做
缓存系统新增了对目录的支持(目录中条目的增删和重命名可以导致缓存未命中),以及“元数据模式”(大小、inode 或 mtime 变化时,无论内容是否变化都视为未命中)。这两种特性可以任意组合,并作为新 API 暴露在构建系统中。
缓存清单改用二进制格式,文件大小减少约 25%;新的 zig cache-cat 子命令可以用于排查或查看 .zig-cache 目录中的文件。缓存系统现在还能解释一次“未命中”的原因。在实测中,缓存命中的速度提升了 5–10%。
引入“配置缓存被污染”的概念
如果 build.zig 中的配置逻辑产生了副作用,或者做了缓存系统无法追踪的事情,就称配置缓存“被污染(poisoned)”了。注意它和构建步骤在执行时是否有副作用无关:一个打印“hello world”的 Run 步骤不会污染缓存,而在配置阶段检查 scdoc 是否存在、并据此决定某个选项的默认值,则会污染缓存。
保持缓存“纯净”可以让 zig build 在配置未变化时直接跳过配置进程。缓存被污染时,执行进程在读取完配置后会将其删除,因为它无法被复用。调用 findProgram,或者更直接地调用 std.Build.Graph.poisonCache,都会污染缓存。更好的做法是通过以下新函数显式声明配置阶段的依赖:
std.Build.dependOnFileContents:配置逻辑依赖某个文件的内容std.Build.dependOnFileMetadata:配置逻辑依赖某个文件的大小、inode、mtime 和内容std.Build.dependOnDirectoryContents:配置逻辑依赖某个目录中的条目std.Build.dependOnDirectoryMetadata:配置逻辑依赖某个目录的最后修改时间
高级用户还可以通过 --cache-poison[=mode] 覆盖这一行为,可选值有 pure(默认,避免错误的缓存命中)、poisoned(不缓存配置)、disallowed(缓存将被污染时直接 panic)和 ignored(无视污染)。
findProgram 与 findProgramLazy
findProgram 会在配置阶段立即在主机上查找一个可能有多个名字的可执行文件,先查找搜索前缀,再查找 PATH 环境变量。由于它会污染配置缓存,只适合配置逻辑确实需要观察程序是否存在(或其输出)的场景。
findProgramLazy 则会创建一个匿名的 Step 来完成查找,返回一个 LazyPath。它不会污染配置缓存,但其结果无法在配置阶段使用;只有当这个 LazyPath 被某个依赖它的步骤用到时,查找才会真正发生。它适合二进制在不同系统上名字不同(例如 python 与 python3),或者二进制可能由源码构建而来的场景。
Run 步骤:透传参数
b.args 被移除,-- 之后的透传参数改为通过 run.addPassthruArgs() 统一添加。构建脚本因此无法再在配置阶段观察到这些参数,但作为交换,修改这些参数时不再需要重新执行构建脚本。
Fmt 步骤的选项
paths 与 exclude_paths 现在是 LazyPath 列表,可以使用便捷函数 b.pathList 创建。
Step.Options 新增 addOptionPathDirectory
添加文件路径类型的选项时,现在需要显式选择:addOptionPath(必须是文件)、addOptionPathDirectory(必须是目录)或 addOptionPathUntracked(不参与依赖追踪)。
惰性依赖更易用
- 抓取惰性依赖时会输出日志
std.Build.dependency现在支持惰性依赖- 新增
std.Build.dependencyLazy,它可能返回error.LazyDependencyNeeded而不是null,因此可以配合try使用 - 当用户的
build函数返回error.LazyDependencyNeeded时,构建系统会去抓取依赖,而不是让配置失败
移除了覆盖 build runner 的能力
“build runner”这个概念已经不复存在,它被拆分成了 configurer 与 maker。过去需要覆盖 build runner 的用例,现在由 Build Server Protocol 来满足。
包管理
所有包管理功能都从编译器中移到了构建系统里,涉及 zig build、zig fetch、zig init、zig libc 与 zig cache-cat 等子命令。这意味着包抓取逻辑、HTTP 客户端与网络、TLS 与相关加密算法、git 协议、xz / gzip / zstd / flate / zip 解压,以及 build.zig.zon 的解析和校验,现在都以源码形式随 Zig 分发,并以 -Osafe 模式(而非 -Ofast)编译。开发构建系统本身时,可以设置环境变量 ZIG_DEBUG_CMD=1 以调试模式编译。
其他变化:
- 修复了路径依赖可以逃逸出父包根目录的 bug
--pkg-path命令行参数和ZIG_LOCAL_PKG_DIR环境变量现在对 fetch 和 build 命令都生效zig fetch现在只抓取到全局缓存;只有使用--save(或其变体)时,才会同时抓取到项目本地的包目录(默认是zig-pkg)。全局抓取时不再要求存在build.zig,这也修复了zig fetch .的回归问题;而zig build总是会抓取到本地(同时也会抓取到全局)
Windows 下 DLL 参数的 PATH 处理略有变化
以前以 Windows 或 Wine 为目标时,Run 步骤中添加的 artifact 参数会根据其递归依赖的 DLL 所在目录修改 PATH。现在只会对 argv[0] 这样处理。
Build Server Protocol
传入 --listen=- 时,构建系统会提供一个协议服务,允许连接的客户端在构建图执行期间对其进行监视和控制。它主要面向 IDE 等第三方工具,目前可以:
- 获取完整的构建图在配置完成后的静态信息,例如有哪些构建步骤、设置了哪些选项、依赖关系等(暴露的模块名集合等少数信息尚未包含)
- 在构建步骤开始和完成时收到通知,包括错误信息以及生成了哪些文件
- 请求构建指定的步骤
官方预计今后 Zig 自己的许多构建工具也会成为 Build Server Protocol 的客户端,并计划让构建服务器复用编译器服务器协议,为编辑器提供类型系统信息、重构等高级能力。
编译器
增量编译
0.17.0 大幅改进了增量编译:修复了大量 bug,上个版本引入的新 ELF linker 也已经很好地支持了这一特性。
因此,现在大多数面向 x86_64-linux 的项目都可以使用增量编译。只需在 zig build 命令后加上 -fincremental --watch(例如 zig build -fincremental --watch),构建系统就会监听源文件的变化,并在变化时执行增量重新构建。
后续版本将继续改进该特性,包括引入新的 Mach-O linker 和对增量编译支持良好的自托管 aarch64 后端、支持不配合 --watch 使用增量编译,以及修复剩余的 bug。
SPIR-V 后端
自托管的 SPIR-V 后端现在和其他后端一样支持多线程。LocalSize、OriginUpperLeft 等执行模式现在由函数的调用约定推导,而不是通过内联汇编设置;新增的 spirv_task 和 spirv_mesh 调用约定支持了 task shader 与 mesh shader。
不再允许在内联汇编中通过 OpCapability 和 OpExtension 声明 capability 和扩展,改为通过目标 CPU 特性(即 -mcpu 选项)启用。这个周期内一共修复了 22 个 SPIR-V 后端的 bug。
aarch64 后端
该后端的进展受制于链接器的改进,而其中许多改进已在本周期完成。
loongarch 后端
社区贡献了 loongarch64 自托管后端的初始实现,目前仍处于实验阶段,尚不可用。
WebAssembly 后端
Zig 的 WebAssembly 后端现在已经通过了 100% 的行为测试(相对于 LLVM 后端)。但由于缺乏调试信息支持,它目前还不是调试模式下的默认后端。
链接器(Linker)
ELF
本版本在“用新实现取代旧的自托管 ELF linker”方面取得了显著进展,具体包括:完整的 x86_64 与 SPARC64 支持、部分 LoongArch 支持、静态库与动态库生成、可执行文件中未定义符号的报错、GOT 生成、copy relocation、GNU 符号版本、DWARF 调试信息、符号哈希表生成、基本可复现的二进制、任意的段对齐,以及对较小主机文件系统块大小的支持。
虽然它还没有完全达到旧版自托管 ELF linker 的功能水平(因此仍默认关闭),但实际上已经能够构建绝大多数面向 x86_64-linux 的 Zig 项目。和 0.16.0 一样,在使用增量编译时会默认启用这个新链接器。官方希望在下一个版本中彻底移除旧的 ELF linker。
COFF
链接器的 COFF 支持得到了大量增强,包括:输出 .obj 对象文件与 .lib 归档,随映像一起输出导入库,输出 TLS 与导出数据目录,接受对象文件、归档和导入库作为输入,按需从归档中链接对象,支持 COMDAT 规则,同时支持链接 -gnu 与 -msvc 的 libc,支持 TLS 与 __dllimport,根据导出符号自动选择入口点,-gnu 下的构造 / 析构函数支持,以及 /INCLUDE、/ALTERNATENAME、/MERGE、/DEFAULTLIB 等 .drectve 参数。
新的链接器测试框架
Zig 的链接器测试正在转向基于快照的方式:结合 objdump 快照对比、实际运行产物以及检查链接器错误来进行测试。
SPIR-V
SPIR-V 链接器被重写,现在支持增量编译,并且可以链接外部的 .spv 对象文件。
Fuzzer(模糊测试器)
尽管本版本的构建系统改动与内置 fuzzer 及其和构建系统的交互有一定关系,但 fuzzer 本身没有变化。官方预计会在之后的版本周期中重点改进它。
Bug 修复
这个版本周期内一共关闭了 329 个 bug 报告。
这个版本仍然包含已知 bug
Zig 仍然存在已知的 bug、错误编译和回归问题。即使使用 0.17.x,在一个不算小的项目中使用 Zig,也可能需要你参与到开发流程中来。当 Zig 到达 1.0.0 时,Tier 1 支持将额外增加一条 bug 策略方面的要求。
0.17.0 中值得注意的已知回归有:
- #37006:x86 上软浮点的 compiler-rt 无法编译
- #36986:
std.debug.simple_panic无法编译;Zig libc 中的弱符号无法被可靠地覆盖 - #36444:配置进程与执行进程的分离破坏了
std.Build.Step.Run中使用响应文件(response file)的场景 - #37050:SPIR-V 后端的回归
工具链(Toolchain)
LLVM 22
本版本升级到了 LLVM 22.1.8,这同样覆盖了 Clang(zig cc)、libc++、libc++abi、libunwind 和 libtsan。
loop vectorization 仍然被关闭
上个版本为了绕过一个影响 Zig 编译器的错误编译问题,被迫关闭了 LLVM 的关键优化 pass——loop vectorization。虽然修复已经合入 LLVM 主分支,但 0.17.0 使用的 LLVM 22 并不包含该修复,因此这个变通方案仍然保持开启。0.18.0 将升级到 LLVM 23,届时可以重新启用这项优化。
musl 1.2.5
0.17.0 分发的是 musl 1.2.5,并附带了向后移植的安全与可移植性修复;上游已经发布了 1.2.6,0.18.0 将会更新到这个版本。静态链接 musl 时,许多函数现在由 zig libc 提供,而不是从 musl 复制而来的源文件。因此如果你遇到了 Zig 提供的 musl libc 的问题,请向 Zig 的 issue 跟踪器报告,而不是 musl 的。
glibc 2.44
交叉编译时现在可以使用 glibc 2.44。
Linux 7.2 headers
本版本包含 Linux 内核 7.2 版本的头文件。
macOS 27.0 headers
本版本包含 macOS 27.0 版本的系统头文件。
MinGW-w64
Zig 分发的 MinGW-w64 更新到了提交 31bd54ab7d5fe03c67ed2bb1a57e531b9c7f8cc4,同样地,许多函数现在由 zig libc 提供。
NetBSD 11.0 libc 与 OpenBSD 7.9 libc
交叉编译时现在可以使用 NetBSD libc 11.0 与 OpenBSD libc 7.9。
WASI libc
0.17.0 继续分发 WASI libc 提交 c89896107d7b57aef69dcadede47409ee4f702ee,其中许多函数现在也由 zig libc 提供。从 0.18.0 开始,Zig 将不再分发第三方的 WASI libc 代码,而是通过 zig libc 为 WASI 目标提供 libc。
zig libc
在 libc.txt 文件中,gcc_dir 字段更名为 cc_dir,以反映它并不专属于 GCC 的事实。旧名称暂时仍然可以使用,但建议尽快迁移。另外,cc_dir 现在在 Linux 目标上是必填的;由于 Android 和 OpenHarmony 把相关的对象文件放在了不寻常的位置,这些目标的用户可能需要把 cc_dir 设置为与 crt_dir 相同的路径。
zig cc
zig cc 与 zig c++ 现在基于 Clang 22.1.8。
zig objdump
新增了 zig objdump 子命令。它是链接器快照测试的基础,也为 COFF 链接器的开发提供了帮助,支持输出文件头、段头、重定位、符号、导入与导出符号、归档成员等信息。
resinator
Windows 资源脚本的编译将在下一个版本中从编译器移到一个官方但独立的构建系统包中。因此,相应的 std.Build 函数与字段(例如 Build.Module.addWin32ResourceFile)已在本版本中被标记为 deprecated。zig rc 子命令会被保留,以继续支持在其他构建系统中使用 Zig 工具链的场景。
zig fmt
新增了 --complexity 参数,用于统计源文件的 token 数与 AST 节点数。相比单纯的行数,它能更有效地衡量一次修改让代码复杂度增加还是减少了。例如,把 std.fmt.allocPrint 改为 arena.print 之后,行数几乎不变,但 --complexity 显示源码复杂度降低了约 1%。
路线图(Roadmap)
官方给出的后续方向是:
- 与 ZLS 团队合作完善 Build Server Protocol,直到满足 ZLS 的所有使用场景
- 完成 x86_64 后端的 Windows 支持,使其可以默认启用
- 完成并稳定语言本身
- 做完 aarch64 后端,并让它成为调试模式的默认后端
- 继续增强链接器,摆脱对 LLD 的依赖,并支持增量编译
- 增强内置 fuzzer,让它可以与 AFL 等业界最先进的 fuzzer 竞争
- 把对 LLVM 的依赖从“链接库”转为“调用 Clang 进程”
- 完善构建系统,尤其是包管理功能
- 审查标准库
如果说 0.16.0 是“大量基础设施重构真正落地”,那么 0.17.0 就是在这个基础上,把构建系统重新打磨成了一个更快、更可缓存、也更容易被外部工具集成的系统,同时让语言本身离“定型”又近了一步。