Hacker News 中文摘要

RSS订阅

Zig 发布 v0.17.0 -- Zig v0.17.0

齐格(Zig)是一种用于维护健壮、最优和可复用软件的通用编程语言和工具链。本次0.17.0版本发布包含5个月的工作,来自206位不同贡献者的925次提交。此发布周期原本预计更短,但最终规模可观,构建系统被重新设计,引入了Build Server Protocol,ELF链接器也获得增强。

正文

0.17.0 发布说明 ⚡ Zig 编程语言

下载与文档

Zig 是通用编程语言和工具链,用于维护健壮、最优、可复用的软件。Zig 开发由 Zig 软件基金会资助,这是 501(c)(3) 非营利组织。请考虑定期捐赠,以便为核心团队成员提供更多计费工时。这是加速项目沿路线图到 1.0 的最直接方式。如果需要捐赠收据,或想从 GitHub Sponsors 迁移,建议通过 Every.org 捐赠。

本次发布包含 5 个月工作:206 位贡献者的 925 次提交。原本预计更短,但发布周期最终内容充实:构建系统重做,包括引入构建服务器协议;ELF 链接器增强到预期增量编译在 x86_64-linux 上对所有人可用。

目标支持

Zig 支持广泛架构和操作系统。支持表和附加平台部分涵盖 Zig 可为其构建程序的目标,而 zig-bootstrap README 涵盖 Zig 编译器本身可轻松交叉编译运行的目标。

值得注意的变化:

  • aarch64-openbsd 现在在 CI 中本地测试,确保后续高质量支持。
  • aarch64-freebsd 和 aarch64-netbsd CI 任务现在也在 pull request 上运行,而不仅是 master 推送。
  • 一个破坏大多数 aarch64-windows 二进制文件(包括 Zig 编译器)的 LLVM bug 已被绕过。
  • Zig 编译器现在在目标为 aarch64-openbsd 时应用强制代码加固技术,以便生成的二进制文件实际工作。
  • Zig 现在在 32 位 ARM 上崩溃和断言失败时提供堆栈跟踪。Thumb-only 目标仍有工作。
  • Zig 现在在 SPARC 上崩溃和断言失败时提供堆栈跟踪。
  • Zig 现在在 AArch64 上做堆栈展开时处理指针认证操作码。
  • 添加对 loongarch32-linux-gnu[sf] 目标的支持。
  • Zig 现在对 64 位 SPARC 有普遍可用支持,尤其是 sparc64-linux。这主要归功于新的 ELF 链接器,它对该目标支持比 LLD 更好。
  • Zig 标准库已移植到 x86-64 的 x32 ABI 和 64 位 MIPS 的 N32 ABI。这些是利基 ILP32 ABI,允许使用 64 位指令集但只有 32 位指针——用地址空间换取更低内存使用和更好缓存利用率。
  • 添加一些游戏机目标信息:aarch64-switch、arm-gba、mipsel-psx、powerpc-wiiu。
  • 添加非常早期的 xtensa-linux 支持。目前只能通过 C 后端或实验性 LLVM 后端使用。
  • Zig 标准库现在在使用 C 后端时支持 arc[eb]-linux、csky-linux 和 m88k-openbsd。
  • Zig 标准库现在支持无 libc 的 microblaze[el]-linux、sh[eb]-linux 和 sparc-linux。
  • Zig 现在对所有 PowerPC 目标强制 -mabi=ieeelongdouble。这只是形式化已有现实;Zig 从未支持 IBM double-double 格式的 long double,可能永远不会。因此本发布放弃 powerpc-linux-gnueabi[hf],因为 glibc 仅在这些目标支持 double-double。powerpc-linux-musleabi[hf] 仍支持,因为使用 IEEE 格式。
  • 本发布放弃 powerpc64-linux-gnu。Zig 只支持链接 64 位 PowerPC 的 ELFv2 二进制,而 glibc 未官方支持大端 ELFv2,也不支持 IEEE long double。
  • Zig 检测原生 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。
  • 在 Zig 目标查询语法中,原生 libc 版本检测现在仅在三元组实际使用原生 libc 时发生(即 ABI 组件省略)。预期新行为更符合人们对目标查询工作方式的思维模型,尤其考虑 OS 组件时。

Tier 系统

Zig 对各种目标的支助级别大致分为四级,Tier 1 最高。目标是 Tier 1 目标零禁用测试——这将成为 1.0.0 后 Zig 发布的要求。

Tier 1
  • 所有非实验语言特性已知正确工作。
  • 编译器可在不依赖 LLVM 的情况下为该目标生成机器码。
  • 集成模糊测试器在该目标工作(如适用)。
Tier 2
  • 标准库跨平台抽象有该目标实现。
  • 断言失败和崩溃在该目标产生堆栈跟踪。
  • 交叉编译时该目标可用 libc(如适用)。
  • CI 机器每次推送都构建该目标的模块测试。
Tier 3
  • 编译器可依赖外部后端如 LLVM 为该目标生成机器码。
  • 链接器可为该目标生成对象文件、库和可执行文件。
Tier 4
  • 编译器可为该目标生成汇编或 C 源代码。

支持表

支持表省略。✅ 完全支持,❌ 无支持,⚠️ 部分支持,❔ 状态大多未知。标有 🪦 的目标是过时的。

OS 版本要求

Darwin 15.0+;DragonFly BSD 6.4+;FreeBSD 14.0+;Linux 5.10+;NetBSD 10.1+;OpenBSD 7.8+;Windows 10+。

附加平台

Zig 对这些目标也有不同程度支持,tier 系统不完全适用。列表从略。

语言变更

语言稳定性进展

自 Zig 0.16.0 以来,语言稳定化取得大量进展。这是路线图关键步骤,也是标记 Zig 1.0 前的要求。特别是自上次发布以来,讨论并决定了许多语言提案——接受约 25 个,拒绝约 125 个。撰写时,Codeberg 问题跟踪器上仍有 23 个未决语言提案,旧 GitHub 跟踪器上仍有 61 个未决。因此,这一努力是最终确定语言设计的重要一步(尽管一些重大决定仍待定)。

@bitCast 变化

Zig 0.17.0 改变 @bitCast 内建的定义。许多情况下新行为等同旧行为:整数类型间转换不受影响,整数类型与 packed struct 或 packed union 之间转换也不受影响。

但涉及数组或向量类型的 @bitCast 调用语义改变。不幸的是,这一变化可能破坏现有代码而不触发编译错误。因此升级时审计涉及数组或向量类型的 @bitCast 使用可能有用。

新定义:@bitCast 将值的逻辑位表示重新解释为不同类型。以下类型被认为有逻辑位表示:void;bool;整数类型(comptimeint 除外);浮点类型(comptimefloat 除外);整数后端类型 enum(T)、packed struct(T)、packed union(T);这些类型的数组或向量。

对整数和浮点类型,逻辑位表示从最低有效位开始到最高有效位结束。对数组和向量类型,所有元素的逻辑位表示按顺序从第一个元素开始连接。

实际上,新 @bitCast 定义在小端目标上大体符合旧行为。与旧行为不同,新行为完全端序无关,即操作无论目标端序如何都相同。

新定义禁止一些之前允许的转换。特别是涉及 extern struct 或 extern union 类型的转换不再允许。多数此类代码意在重新解释值的内存表示(有时称“类型双关”)——要实现这个,用 @ptrCast 或 extern union。

对 extern struct 使用 @bitCast 会编译错误;改用 @ptrCast。

@bitCast 现在在目标类型为 enum 时也执行无效标签值安全检查。

还添加 std.meta.BackingInt 获取 @backingInt 的结果类型。

Zig 现在要求空枚举的后端整数为 noreturn,因为它们不可实例化。

形式化指定与模糊测试的语法

Zig 的形式 grammar.peg 和实际语言实现许多地方不一致。形式语法可能从未 100% 匹配手写词法分析器和解析器。这个问题现已修复,为未来语法变更和语言规范工作解除障碍。

高层方法:编写工具接受 grammar.peg 输入并输出简单递归下降解析器。生成解析器用作模糊测试 oracle,手写 std.zig.Ast.parse() 与其比较。这确保单一事实来源,允许语法变化时轻松迭代。详情见 #36094。

C 翻译移至外部包

@cImport 在 Zig 0.16.0 已废弃,现在移除。此外,本发布中 std.Build.Step.TranslateC 废弃,改为显式依赖官方 ZSF translate-c 包。该包提供相同实现,但提供更多翻译代码配置选项,并有独立于主 Zig 工具链的发布节奏。

升级:zig fetch --save git+https://codeberg.org/ziglang/translate-c,build.zig 中改用 Translator。

添加 @backingInt 和 @fromBackingInt

@backingInt 和 @fromBackingInt 是新内建。它们替代已废弃的 @intFromEnum 和 @enumFromInt (#35966)。

@backingInt 适用于所有枚举和仅带显式后端整数类型的 bitpack。也适用于标签联合,返回活动标签值的后端整数。undefined 枚举或 bitpack 产生 undefined 后端整数。

@fromBackingInt 推断结果类型,可为任意枚举或带显式后端整数类型的 bitpack。参数正好是那个后端整数类型。对枚举,传 undefined 或会产生无效标签值的后端整数导致安全检查的非法行为。对 bitpack,传 undefined 后端整数产生 undefined bitpack。

@bitCast 现在也在目标类型为 enum 时执行无效标签值安全检查。

还添加 std.meta.BackingInt 获取 @backingInt 的结果类型。

Zig 现在要求空枚举的后端整数为 noreturn,因为它们不可实例化。

升级示例:@enumFromInt 改为 @fromBackingInt(@intCast(...)),@intFromEnum 改为 @backingInt(...)。zig fmt 自动执行此升级。

添加 @SpirvType

SPIR-V 有许多类型,如图像和采样器,在 Zig 类型系统中没有等价物。以前只能通过内联汇编引用,无法将纹理或存储缓冲区声明为普通全局变量。Zig 0.17.0 实现已接受提案 #35240,添加 @SpirvType 内建,与其他类型创建内建并列:

@SpirvType(comptime options: std.lang.Type.Spirv) type

  • .sampler 创建 OpTypeSampler。
  • .image 创建 OpTypeImage。
  • .sampled_image 从 usage 为 .sampled 的图像类型创建 OpTypeSampledImage。
  • .runtime_array 创建 OpTypeRuntimeArray。支持索引并暴露 len 字段,如同数组类型。

非 SPIR-V 目标使用此内建是编译错误。选项也根据目标 OS 验证。

示例:定义图像、采样图像、texture、uvin、colorout,导出 main 使用 std.spirv.imageSampleImplicitLod。用 zig build-obj spirv_type.zig -target spirv32-vulkan 构建。

移除数组乘法语法

数组乘法语法(a ** b)已移除,改用 @splat。

迁移:var result = [1]u8{0} * window_size; 改为 var result: [window_size]u8 = @splat(0); .data = [1]Float{0} * data_size 改为 .data = @splat(0)。

添加 @divCeil

新 @divCeil 内建执行向正无穷舍入的整数除法,补充现有 @divTrunc、@divFloor、@divExact。

@divCeil(5, 3) == 2;@divCeil(-5, 3) == -1。

与其他除法内建一样,调用者保证 denominator != 0 且结果不溢出。不再需要 std.math.divCeil(a, b) catch unreachable。

@hasDecl 仅对公共声明返回 true

以前 @hasDecl 对公共声明和同文件声明返回 true。现在行为与 @hasDecl 在哪个文件无关。

示例:Foo 有 bar、const baz、pub var quux。@hasDecl(Foo, "bar") false;@hasDecl(Foo, "baz") false(0.17.0 不同);@hasDecl(Foo, "quux") true。

允许 comptime 长度切片的解引用和强制转换为数组指针

现在允许:

const slice: []const u16 = &.{1,2,3}; const array: [3]u16 = slice.*; const array_ptr: *const [3]u16 = slice;

移除 void{} 语法

void{} 不再有效,用 {} 代替 (#15213)。

移除 errdefer 捕获

errdefer |err| { ... } 捕获不再允许 (#23734)。迁移:将函数拆成两个,外层用 processOneTargetInner(job) catch |err| std.debug.panic(...),内层返回 !void。

移除 i0

i0 不再是允许的原始整数类型。该类型无意义,几乎可以肯定透明替换为 u0。

移除 internal 和 link_once 全局链接

std.lang.GlobalLinkage 的 internal 和 linkonce 标签已移除,因为语义不明确且代码生成和链接支持不完整 (#36956)。linkonce 用途可能由 weak 满足,internal 替代是干脆不 @export 符号。

标准库

  • 基于 Ping Tak Peter Tang 的“IEEE 浮点算术中指数函数的表驱动实现”,添加 f128 对 @exp 和 @exp2 的支持,适配 128 位数字 (#31846)。
  • 添加 std.Io.Semaphore.waitTimeout (#31924)。
  • 添加 std.spirv 辅助函数用于采样、查询和写入图像 (#36187)。
  • ArrayHashMap.setKey 不再重新计算整个索引 (#32136)。
  • std.Target.parseCpuModel 现在返回 optional 而非 error。
  • std.debug.Pdb:去重内联源位置 (#35438)。
  • hash.crc 完整命名空间审计 (#35952)。
  • std.fs.path 为 relative 和 resolve 添加追加变体 (#36784)。

废弃

  • std.heap.memorypool.AlignedManaged 移除,改用 std.heap.memorypool.Aligned。
  • std.heap.memorypool.ExtraManaged 移除,改用 std.heap.memorypool.Extra。
  • 废弃 std.builtin,改用 std.lang。
  • 废弃 std.meta.fieldInfo、fieldNames、fieldTypes,改用 @typeInfo。
  • 废弃 std.DoublyLinkedList.pop,改用 popLast。
  • std.gpu 重命名为 std.spirv。
  • 移除 std.ascii.indexOfIgnoreCase 等,改用 find 版本。
  • 移除 std.bit_set.Integer.initEmpty/initFull、Array.initEmpty/initFull、std.enums.EnumSet.initEmpty/initFull,改用 empty/full。
  • 移除 std.mem.containsAtLeastScalar2、readPackedIntNative/Foreign、writePackedIntNative/Foreign,改用统一版本。

StackFallbackAllocator 重做

StackFallbackAllocator 用于“小 vec”优化,常见情况可放在预分配栈缓冲区,罕见情况需动态堆分配。旧设计问题:无法指定缓冲区对齐;泛型于缓冲区大小;调用 .allocator().get() 会改变类型,与其他分配器不同,要求运行时安全检查。

现在缓冲区作为参数传入,像大多数其他需要缓冲区的 std API。

迁移:var stack align(...) = std.heap.stackFallback(...); const allocator = stack.get(); 改为 var stackbuf: [1]Item = undefined; var stack: std.heap.StackFallbackAllocator = .init(@ptrCast(&stackbuf), gpa); const allocator = stack.allocator();

引入 SafeAllocator

std.heap.DebugAllocator 被线程安全分配器替换,保证:

  • deinit 报告所有泄漏并释放所有后端内存。
  • 所有分配不匹配导致 panic 或段错误。
  • 来自其他 SafeAllocator 实例的分配导致 panic(如果 Options.canary 不同)。
  • 双重释放和操作(resize、remap、free)竞争 panic 或段错误。

鉴于后端分配器不重用内存,它也不重用内存,释放后大多数写入将段错误或最终检测并 panic。

std.heap.DebugAllocator 和 std.heap.Check 已废弃。

每次分配尾部有 AllocFooter,包含分配元数据和堆栈跟踪。受校验和保护,捕获分配覆写损坏并报告 canary 不匹配。分配内存最小对齐为 AllocFooter,使 footer 位于由分配大小决定的固定偏移。分配内存存储于:

  • 小分配在线性填充桶内。
  • 或直接从后端分配器分配。

跟踪分配时,每线程维护后端分配表。生产者-消费者操作中其他线程可能修改表,因此表是链表,仅通过创建新段扩展。每线程维护空闲条目链表,可能包含其他线程表的条目。

生产者-消费者操作假定外部提供 acquire/release 顺序。其他重用内存的线程安全分配器也假定这点,否则重用已分配内存会有数据竞争。

添加两个模糊测试。多线程模糊测试启动若干工作线程用于所有测试运行。在 TSAN 下广泛运行。

基准:使用 -Osafe 编译器构建和 -Ddebug-allocator 构建标准库测试。对比 master 与 branch:walltime 29.4s -> 22.1s (-24.7%);peakrss 2.24GB -> 1.11GB (-50.3%);cpucycles 143G -> 136G (-4.4%);instructions 268G -> 273G (+1.6%);cachereferences 13.1G -> 12.3G (-6.0%);cachemisses 2.38G -> 2.02G (-14.9%);branchmisses 634M -> 569M (-10.2%)。

ArrayList

  • getLastOrNull 废弃,重命名为 last。
  • getLast 废弃,改用 last 加 .?。
  • 添加 lastPtr,返回 ?*T。

升级:if (list.getLastOrNull()) |foo| 改为 if (list.last()) |foo|;const foo = list.getLast(); 改为 const foo = list.last().?;

ArrayList 指针稳定性增强,帮助更快追踪 ArrayList 使用 bug (#36239)。见 Devlog Entry。

debug.SafetyLock 获得共享锁支持

现有 lock 和 unlock 方法继续独占,应在数据可能被修改时使用。新方法 lockShared 和 unlockShared 可用于多个独立用户读取但不修改数据的共享锁定。

fmt.allocPrint 移至 mem.Allocator

try std.fmt.allocPrint(arena, "{s}={d}", .{x, y}); 改为 try arena.print("{s}={d}", .{x, y});

格式化打印增强

"{q}" 指定符转义字符串以便出现在双引号字符串字面量中,现在放宽转义规则,UTF-8 编码数据可原样通过。

引入 "{qf}" 用于双引号转义 format() 输出。

std.zon.parse 重做

std.zon.parse 现在接受结构体参数,并从 arena 分配结果。

迁移:std.zon.fromSlice(MyZonType, gpa, source, &diag, .{}) 改为 std.zon.fromSlice(MyZonType, .{ .gpa = gpa, .arena = arena, .source = source, .diagnostics = &diag })。错误处理调整。

一些方法重命名:fromSliceAlloc -> fromSlice;fromSlice -> fromSliceNoAlloc;其他 from 方法同样重命名。

添加 updateFrom 变体如 updateFromSlice。这些更新内存值,用 ZON 源中字段覆写值字段。可用于加载不同优先级配置文件,例如文本编辑器有全局配置和每项目配置。

重命名 bit_set 变体并废弃 managed 版本

为一致性重命名类型,废弃旧名和 managed 变体。

  • std.bitset.IntegerBitSet -> std.bitset.Integer
  • std.bitset.ArrayBitSet -> std.bitset.Array
  • std.StaticBitset、std.bitset.StaticBitSet -> std.bitset.Static
  • std.DynamicBitSetUnmanaged、std.bitset.DynamicBitSetUnmanaged -> std.bitset.Dynamic
  • std.DynamicBitSet、std.bitset.DynamicBitSet -> std.bitset.DynamicManaged(废弃)

std.lang.Type 采用结构数组风格

类型反射时,结构体和联合以结构数组风格返回信息 (#35234)。

示例:@typeInfo(S).@"struct".fields 遍历改为 info.fieldnames 和 info.fieldtypes 并行遍历。

重命名 lang.OptimizeMode 为 lang.Optimize

并从枚举标签名移除 "release"。无功能变化,但在本补丁中添加向后兼容声明后仍是破坏性,因为使用 == 或 != 的表达式无法使用废弃名称。

  • std.lang: OptimizeMode -> Optimize
    • Debug -> debug
    • ReleaseSafe -> safe
    • ReleaseFast -> fast
    • ReleaseSmall -> small

lang.Optimize.runtimeSafety

std.lang.Optimize.runtimeSafety 优于 std.debug.runtime_safety,因为它将向调用点提供关于自身模块而非标准库模块的知识。

@import("builtin") 废弃

@import("builtin") 中冗余常量 cpu、os、abi、object_format 已废弃,将在 0.18.0 移除。请用 target 常量对应字段:

  • cpu -> target.cpu
  • os -> target.os
  • abi -> target.abi
  • object_format -> target.ofmt

mem.eql 和 mem.findDiff 正确处理浮点

std.mem.eql 和 std.mem.findDiff 在两个输入是同一内存切片时短路。该短路仅在给定类型的 == 运算符自反时正确。浮点不是,例如 std.math.nan(f64) != std.math.nan(f64)。此改变在浮点切片上禁用该优化。

以前失败现在成功的测试:const x: [3]f64 = .{42.0, std.math.nan(f64), 3.1415}; expect(!std.mem.eql(f64, &x, &x)); expectEqual(1, std.mem.findDiff(f64, &x, &x));

解耦 Uri 和 net.HostName

Uri 有时对 host 使用 HostName.validate(在 resolveInPlace 中),有时不用(在 parseAfterScheme 中)。这本身是问题,但更大问题是 RFC3986(Uri)对有效主机名的定义与 RFC1123(HostName)大不相同,因此仅让 Uri 一致使用 HostName.validate 会降低 Uri 整体实用性。

相反,所有 HostName 相关内容已从 Uri 移除。Uri.getHost 移至 HostName.fromUri(无优雅废弃,因为语义不同,用户需评估使用点),Uri.getHostAlloc 完全移除。

迁移:var hostbuf: [HostName.maxlen]u8 = undefined; const host = try uri.getHost(&hostbuf); 改为 const host = try HostName.fromUri(uri, &hostbuf); 注意错误集不同,因为 fromUri 做验证。(#36036)

构建系统

  • b.build_root (Directory) -> b.root (Path)
  • ConfigHeader.Options: includeguardoverride -> include_guard
  • LazyPath: getDisplayName -> format ("{f}")
  • LazyPath.basename 移除,因为值到 make 阶段才知
  • b.findProgram 分为 findProgram 和 findProgramLazy,API 面向未来
  • ConfigHeader 修复;现在为所有风格报告未使用值
  • addArtifactArg、addPrefixedArtifactArg -> addArtifactArg2
  • addOutputFileArg、addPrefixedOutputFileArg -> addOutputFileArg2
  • addFileContentArg、addPrefixedFileContentArg -> addFileContentArg2
  • addOutputDirectoryArg、addPrefixedOutputDirectoryArg -> addOutputDirectoryArg2
  • addDirectoryArg、addPrefixedDirectoryArg、addDecoratedDirectoryArg -> addDirectoryArg2
  • addDepFileOutputArg、addPrefixedDepFileOutputArg -> addDepFileOutputArg2
  • addFileArg、addPrefixedFileArg -> addFileArg2

将 Maker 进程与 Configurer 进程分离

zig build 现在在单独可执行文件中运行项目 build.zig 代码,而不是执行包管理和构建图的可执行文件。这使 zig build 更快,原因:

  • 编辑 build.zig 时 maker 可执行文件保持不变,因此安装 Zig 后只需构建一次(“首次设置”)。
  • maker 可执行文件以优化启用构建,随着 --watch 和 --fuzz 引入越来越有价值。
  • 根据 zig build 使用的 CLI 标志,有时可跳过 build.zig 逻辑。

此外,配置现在序列化为紧凑二进制格式,可被第三方工具消费,也是新构建服务器协议的一部分。之前通过 fork 构建运行器满足此用例的方式不再支持。

要将配置以 .zon 渲染到 stdout,传 --print-configuration。

缓存系统重做

新功能:

  • 目录支持。目录中条目添加、删除或重命名可导致缓存未命中。
  • 元数据模式。通常只有内容变化导致缓存未命中。元数据模式下,大小、inode 或 mtime 变化总是导致缓存未命中,与内容无关。

四种组合都可行 (isdirectory=true/false, metadatamode=true/false)。这些功能在构建系统中作为新 API 暴露。

由于 Zig 工具链严重依赖缓存系统,本发布也切换到二进制格式,节省约 25% 文件大小,减轻文件系统缓存压力,简化计算机工作——直接从磁盘复制字节而非解析文本文件。新 zig cache-cat 子命令可用于故障排除或调试 zig-cache 目录内文件。

缓存系统现在还能解释“未命中”原因。std.Build.Cache 公开 API 有许多破坏性变化,但在编译器工具之外,这是不常见 API,所有变化都使其更难误用。

观察到此变化使缓存命中加速 5-10% (#36822)。

引入配置缓存污染概念

如果缓存被污染,意味着配置逻辑有副作用,或做了缓存系统无法跟踪的事。

不要与单个步骤评估时可能有副作用混淆;这与 build.zig 本身逻辑有关。例如,Run 步骤打印 "hello world" 在 make 时有副作用,因此不应设置此标志,而在配置时检查 scdoc 是否存在以选择配置选项默认值则应设置。

保持缓存纯净将使 zig build 更快,当生成相同配置时绕过 configurer 进程。

缓存被污染时,maker 进程在摄取构建配置文件后将其删除,因为无法重用。

污染缓存的方式包括调用 findProgram,或更直接 std.Build.Graph.poisonCache。比缓存污染更好的替代方案是用新函数显式声明配置依赖:

  • std.Build.dependOnFileContents:表明 build.zig 逻辑依赖特定文件内容。
  • std.Build.dependOnFileMetadata:表明依赖特定文件大小、inode、mtime 和内容。
  • std.Build.dependOnDirectoryContents:表明依赖特定目录条目。
  • std.Build.dependOnDirectoryMetadata:表明依赖特定目录最后修改日期。

高级用户可用新 CLI 选项覆盖缓存污染行为:

--cache-poison[=mode] 覆盖配置缓存行为 pure(默认)避免假阳性缓存命中 poisoned 不缓存配置 disallowed 缓存将被污染时 panic ignored 一点毒不会伤害任何人

findProgram

立即(配置阶段)在主机上搜索有多个可能名称的可执行文件。名称按顺序搜索,先观察搜索前缀,然后 PATH 环境变量。

调用此函数会污染配置缓存,因此仅当配置逻辑需要观察程序存在或输出时才合适。这就是现在还有 findProgramLazy 的原因。

findProgramLazy

创建匿名 Step,在主机上搜索有多个可能名称的可执行文件。

与 findProgram 不同,此函数不污染配置缓存,但结果不能用于配置阶段,因此返回类型为 LazyPath。

返回找到的可执行文件 LazyPath。仅当依赖 Step 将使用 LazyPath 时才搜索。

适用场景:

  • 二进制在所有系统上名称不同(例如 "python" vs "python3")。
  • 二进制可能从源码构建而非全局安装,因此可能在某个搜索前缀路径中找到。

Run 步骤:透传参数

Run 步骤中,透传参数现在集中在一起,配置阶段无法观察是否提供运行参数。

之前:if (b.args) |args| { runcmd.addArgs(args); } 改为 runcmd.addPassthruArgs();

这从构建脚本移除一种能力,因为它们不再能观察这些参数。作为交换,改变这些参数时,构建脚本不再必须从源码重建。

Fmt 步骤:选项

paths 和 exclude_paths 现在是 LazyPath 列表。有便利方法创建:b.pathList。

之前 const fmtincludepaths = &.{...}; 改为 const fmtincludepaths = b.pathList(&.{...});

Step.Options:添加 addOptionPathDirectory

现在添加文件路径选项时,必须显式选择:

  • addOptionPath(必须是文件)
  • addOptionPathDirectory(必须是目录)
  • addOptionPathUntracked(选择退出依赖跟踪)(#36876)

惰性依赖易用性增强

  • 惰性依赖被获取时记录日志。
  • std.Build.dependency:支持惰性依赖。
  • 引入 std.Build.dependencyLazy,可能返回 error.LazyDependencyNeeded 而非 null,以便使用 try。
  • 当用户 build 函数返回 error.LazyDependencyNeeded 时,构建系统继续获取它们而非使配置失败。

移除覆盖构建运行器的能力

不再有“构建运行器”概念;它已拆分为 configurer 和 maker。

此用例现在由构建服务器协议处理。

包管理

所有包管理功能已从编译器移入构建系统。包括子命令:zig build、zig fetch、zig init、zig libc、zig cache-cat。

这意味着以前包含在编译器可执行文件中的大部分内容现在以源代码形式提供,包括:包获取逻辑;http 客户端和网络;TLS 及相关加密;git 协议;xz、gzip、zstd、flate、zip;解析、验证和处理 build.zig.zon 文件。

所有这些功能现在以 -Osafe 优化模式编译,而不是编译器中的 -Ofast。当自行修改构建系统时,环境变量 ZIGDEBUGCMD=1 可用于以调试模式编译构建系统。

杂项变化:

  • Bug 修复:拒绝逃逸父包根的路径依赖。
  • --pkg-path CLI 参数和 ZIGLOCALPKG_DIR 环境变量现在对 fetch 和 build 命令都生效。
  • 全局与本地获取:zig fetch 只获取到全局缓存,如以前。但如果使用 --save(或任何变体),也获取到本地包路径。全局获取时不要求存在 build.zig。zig build 总是本地获取(也全局获取)。这修复了回归用例 zig fetch .。
  • 按路径获取时,总是计算哈希,总是创建重新压缩的 tarball,总是覆写任何现有全局缓存条目。

DLL 参数的 PATH 微差

以前,当目标为 Windows 或 Wine 时,添加到 Run 步骤的 artifact 参数会根据包含递归 DLL 依赖集的目录修改 PATH。现在仅对 argv[0] 这样做。对其他命令行参数也这样做的动机不明,因为执行 argv[0] 不需要加载那些 DLL。

构建服务器协议

现在,当传入 --listen=- 时,构建系统提供协议,允许连接客户端监控和控制执行中的构建图。旨在供第三方工具如 IDE 消费。

当前可以:

  • 获取整个构建图的静态、配置后信息,如哪些构建步骤可用、设置了哪些选项、依赖等。还有一些尚未包含,如暴露的模块名集合。
  • 当构建步骤开始和完成时收到通知,包括错误信息和生成的文件。
  • 请求特定步骤构建。

特别是,maker 进程和 configurer 进程的分离是破坏性变化,使 ZLS 项目无法与 0.17.0 工作。尽管本发布周期中恢复功能取得一些进展,Zig 团队和 ZLS 团队仍在合作进一步增强构建服务器协议,使 ZLS 不仅能恢复功能,还能超越之前的权力和能力。

未来预期 Zig 自己的第一方构建系统工具大部分将成为构建服务器协议客户端,以自用确保第三方工具享有同等能力 (#36497)。

还计划让构建服务器为编译步骤多路复用编译器服务器协议,提供类型系统信息、重构和其他高级编辑能力 (#615)。

编译器

增量编译

Zig 编译器的增量编译实现——允许更改代码后近乎即时重建项目的功能——在 Zig 0.17.0 中显著改进。许多 bug 已修复,上一版本引入的新 ELF 链接器获得良好支持。

得益于这些增强,现在大多数 x86_64-linux 目标项目可以利用增量编译。为此,在 zig build 命令添加 -fincremental --watch(例如 zig build -fincremental --watch)——这将使 Zig 构建系统监听源文件变化,并通过执行增量重建做出反应。

更多使用信息或内部原理,见 Zig 核心团队成员博客文章。

未来版本将继续改进此功能,包括引入新 Mach-O 链接器和自托管 aarch64 后端并良好支持增量编译;添加不使用 --watch 的增量编译支持;修复剩余 bug。

SPIR-V 后端

自托管 SPIR-V 后端现在像其他后端一样多线程。

执行模式如 LocalSize 和 OriginUpperLeft 现在从函数调用约定推导,而不是通过内联汇编设置,新 spirvtask 和 spirvmesh 调用约定添加对任务和网格着色器的支持 (#35676)。

不再允许在内联汇编中用 OpCapability 和 OpExtension 声明能力和扩展。它们通过目标 CPU 特性启用,即 -mcpu 选项。

本发布周期修复 22 个 SPIR-V 后端 bug。

aarch64 后端

进展受阻于链接器增强,本发布周期完成许多。

loongarch 后端

loongarch64 自托管后端初始实现已贡献 (#36418)。仍实验性且尚不可用。两种贡献方式:直接开发,或贡献 AIR Legalization Features (#36719),帮助所有未完成后端更快到达终点。

WebAssembly 后端

Zig 的 WebAssembly 后端现在通过 2060/2054(100%)行为测试,与 LLVM 后端相比。但由于缺少调试信息支持,在 debug 优化模式编译时仍不是默认 (#37032)。

链接器

ELF

本发布在将 Zig 旧自托管 ELF 链接器替换为上一版本引入的新实现方面取得重大进展。具体增强:

  • 完整 x86_64 支持
  • 完整 SPARC64 支持
  • 部分 Loongarch 支持
  • 静态库生成
  • 共享库生成
  • 可执行文件中未定义符号报错
  • GOT 生成
  • Copy 重定位
  • GNU 符号版本化
  • DWARF 调试信息
  • 符号哈希表生成
  • 大多可重现二进制
  • 任意节对齐
  • 支持小主机文件系统块大小

虽然此链接器尚未与旧自托管 ELF 链接器完全功能对等,因此默认仍禁用,但实践中已能构建绝大多数 x86_64-linux 目标 Zig 项目。这解锁这些项目使用增量编译——如 Zig 0.16.0,新链接器在这种情况下默认启用。

下一版本希望完全淘汰旧 ELF 链接器,改用此实现。

COFF

链接器 COFF 支持增强,功能 (#35674):

  • 输出对象 (.obj) 和归档 (.lib)
  • 输出图像旁的 implibs
  • 输出图像的 TLS 和 Exports 数据目录
  • 消费对象、归档和导入库作为输入
  • 仅按需从归档链接对象,即如果它们包含满足引用所需的符号
  • COMDAT 规则(足以链接 compiler_rt 和 libc,部分 COMDAT 类型尚不支持)
  • 支持链接 -gnu 和 -msvc libc
  • TLS 支持
  • __dllimport 支持:直接从 IAT 间接调用/加载
  • 根据导出符号检测选择哪个入口点
  • -gnu:构造函数/析构函数支持(合并 .ctor 和 .dtor,设置 CTORLIST、DTORLIST 符号)
  • 支持若干 .drectve 参数(正确链接 msvc libc 所需):/INCLUDE 强制引用符号;/ALTERNATENAME 添加符号别名;/MERGE 节合并,也用于将某些节导向正确位置(如 .ctor/.dtor 到 .rdata);/DEFAULTLIB 添加新输入

新链接器测试框架

Zig 正转向基于快照的链接器测试。

测试结合比较 objdump 快照输出、实际运行产物和检查链接器错误。

zig build -Dlink-snapshot-update 使测试以输出快照而非检查快照的模式运行。

添加新测试的典型工作流:

  1. 添加测试
  2. zig-debug build test-link -Dtest-filter=my-test -Dlink-snapshot-update:为 verifyObjdump 调用中定义的所有快照组合输出 .dmp 文件。单个快照故意在多个目标间别名以减少快照文件夹噪音,因此快照更新由该快照名称第一个运行的测试进行。如果目标间存在差异,将在第 4 步揭示。
  3. 检查快照输出正确性。
  4. zig-debug build test-link -Dtest-filter=my-test:现在针对新添加快照运行所有目标。
  5. 如果出现快照失败,意味着不同目标有不同快照输出。应检查输出看这些结果是否确实是有效差异。如果是,应使用 scope 参数使 -Dlink-snapshot-update 将快照输出到不同文件名,按差异划分。
  6. 重新运行 -Dlink-snapshot-update 更新新快照集。

SPIR-V

SPIR-V 链接器已重写 (#36828)。现在支持增量编译,并可以链接外部 .spv 对象文件。

模糊测试器

虽然本发布的构建系统变化与 Zig 集成模糊测试器及其与构建系统交互松散相关,但模糊测试器本身无变化。

预期未来发布周期专注于改进模糊测试器。

Bug 修复

本发布周期关闭 329 个 bug 报告。完整列表见 GitHub 和 Codeberg 跟踪器。许多 bug 在本发布周期引入并解决。大多数 bug 修复为简洁省略。

本发布包含 bug

Zig 有已知 bug、误编译和回归。即使使用 Zig 0.17.x,在非平凡项目中使用 Zig 可能仍需参与开发过程。

当 Zig 达到 1.0.0,Tier 1 支持将获得 bug 策略作为额外要求。

值得注意的回归

0.17.0 已知以下值得注意的回归:

  • #37006:compiler-rt 在 x86 上软浮点编译失败
  • #36986:std.debug.simple_panic 编译失败
  • #36986:弱 Zig libc 符号无法可靠覆盖
  • #36444:maker 与 configurer 进程分离破坏 std.Build.Step.Run 的响应文件用例
  • #37050:SPIR-V 后端回归

工具链

LLVM 22

本发布升级到 LLVM 22.1.8。涵盖 Clang (zig cc)、libc++、libc++abi、libunwind 和 libtsan。

因回归禁用循环向量化

上一版本中,被迫禁用关键 LLVM 优化 pass——循环向量化,以绕过影响 Zig 编译器的误编译。自首次引入该变通方案,修复已合并到 LLVM 主分支。但修复在 Zig 0.17.0 使用的 LLVM 22 中不可用。因此变通方案目前仍启用。

Zig 0.18.0 将升级到 LLVM 23,因此允许重新启用此优化 pass。

musl 1.2.5

Zig 0.17.0 分发 musl 1.2.5 加上回溯安全和可移植性修复。同时上游已标记 1.2.6。Zig 0.18.0 将更新到 musl 1.2.6。

当静态目标 musl 时,许多函数现在由 zig libc 提供,而非从 musl 复制的源文件。因此,如果遇到 Zig 提供的 musl libc bug,请尊重上游,报告到 Zig 问题跟踪器而非 musl。

glibc 2.44

交叉编译时 glibc 2.44 可用。

本发布包含 Linux 内核 7.2 头文件。

本发布包含 macOS 系统头文件 27.0。

MinGW-w64

Zig 0.17.0 分发 MinGW-w64 提交 31bd54ab7d5fe03c67ed2bb1a57e531b9c7f8cc4。

但许多函数现在由 zig libc 提供,而非从 MinGW-w64 复制的源文件。因此,如果遇到 Zig 提供的 MinGW-w64 libc bug,请报告到 Zig 问题跟踪器而非 MinGW-w64。

NetBSD 11.0 libc

交叉编译时 NetBSD libc 11.0 可用。

OpenBSD 7.9 libc

交叉编译时 OpenBSD libc 7.9 可用。

WASI libc

Zig 0.17.0 继续分发 WASI libc 提交 c89896107d7b57aef69dcadede47409ee4f702ee。

但许多函数现在由 zig libc 提供,而非从 WASI libc 复制的源文件。

此外,从 Zig 0.18.0 开始,不再分发第三方 WASI libc 代码,而是通过 zig libc 为 WASI 目标提供 libc。更多信息见:定义独特 Zig libc ABI #35379;移除 wasi-libc 支持 #36633。

zig libc

在 libc.txt 文件中,gccdir 字段重命名为 ccdir,以反映其不特定于 GCC 的现实。旧名目前仍接受,但鼓励用户迁移 libc.txt 到新名 (#36951)。

此外,ccdir 现在在 Linux 目标上必需。注意,因为 Android 和 OpenHarmony 将相关对象文件存储在异常位置,这些目标的用户可能希望将 ccdir 设置为与 crt_dir 相同路径。

zig cc

zig cc 和 zig c++ 现在基于 Clang 22.1.8。

zig objdump

这是快照测试的要求,也有助于开发 COFF 链接器。

支持功能:用法 zig objdump [options] file。选项包括 -h/--help;--all-headers;--exports[=sort];--file-headers;--imports;--linker-member[=1|2|longnames];--member-headers;--elements=[e1],[e2],-[e3]...(用于快照测试,元素有 file-type、header-name、member-path、newlines、table-header、all);--only-member、--only-section、--only-symbol;--redact=[kind](rva、va、ord、size、all,用于快照测试);--relocs;-s/--snapshot(别名 --redact=all --elements=-all);--section-headers;--strings;--symbols;--tls。

示例输出省略。重定向和元素移除功能用于移除对特定测试不重要的输出部分,以免因无关变化导致假失败。例如符号的 RVA 因链接器无关变化改变。 -s 是简写,启用所有重定向并移除所有额外输出元素,但特定测试若关心特定值可能不使用。

resinator

Windows 资源编译移至外部包

Windows 资源脚本编译将在下一版本从编译器移出到官方但外部的构建系统包 (codeberg.org/ziglang/rc)。因此,相应 std.Build 函数和字段(Build.Module.addWin32ResourceFile 等)在本发布中标记为废弃。

zig rc 子命令在此变化后仍保留,以继续支持使用 Zig 工具链与其他构建系统的用例。

zig fmt

添加 --complexity 标志

简单工具,用于计数 token 和 AST 节点,可用于分享源文件编辑如何增加或减少复杂度,用比行数更有洞察力的启发式。

例如运行在旧 ELF 链接器目录:列出各文件 tokens 和 nodes,总计 tokens=97031 nodes=47621。

另一例是编辑 Lld.zig 使用 arena.print 而非 std.fmt.allocPrint 后,行数大致相同但 --complexity 显示:之前 tokens=11929 nodes=5900;之后 tokens=11832 nodes=5852(源复杂度减少 1%)。

未来此指标可能有助于自动导入组织器决定是否创建导入别名。

路线图

  1. 与 ZLS 团队合作增强构建服务器协议,满足其所有用例。
  2. 完成 x86_64 后端的 Windows 支持,使其可默认启用。
  3. 完成并稳定语言。
  4. 完成 aarch64 后端,使其成为 debug 模式默认后端。
  5. 增强链接器实现,消除对 LLD 的依赖并支持增量编译。
  6. 增强集成模糊测试器,使其与 AFL 和其他最先进模糊测试器竞争。
  7. 从对 LLVM 的库依赖过渡到对 Clang 的进程依赖 (#16270)。
  8. 完成构建系统,特别是包管理功能。
  9. 审计标准库 (#1629)。

评论

社区讨论气氛轻松带技术闲聊,主轴围绕 Zig 0.17.0 发布后的项目状态与创始人对 AI 立场的务实软化,少数人吐槽募捐文化,无激烈分歧。

Zig 创始人正务实接纳 LLM 辅助查错

  • 有人指出 Andrew 受 SQLite 启发,开始将大模型视为发现 bug、迈向无错软件的工具,这会让记得其曾强硬反 AI 的人意外(较多人提及)。 > 或许令一些人意外,Andrew 正因 SQLite 先例而看好用大模型找 bug,当作无 bug 软件之路的工具。(@audunw)

吐槽项目募捐后创始人可能反咬捐赠者

  • 有人引用募捐文案提醒,虽语言优秀,但别指望领导人会客气,捐钱后可能被公开吐槽(少数人)。 > 别惊讶如果项目一把手后来吐槽你或你的公司,不过语言确实不错。(@ai_critic)

对循环向量化依旧禁用感到遗憾

  • 理解升级 LLVM 工作量巨大,但循环向量化功能仍未启用让使用者沮丧(少数人)。

戏谑应主动拥抱机器以免被用

  • 一条玩笑评论催促创始人清醒,机器已在人类之中,不用它们就会被它们用,折射社区对 AI 议题的调侃情绪(纯玩笑代表情绪)。

有价值的补充

  • 有人贴出 Zig 0.17.0 官方发布页面,可供查看版本详情。
  • 推荐观看 Zig 状态演讲视频,深入了解项目动向。