关于 zig-0.16
zig-0.16 是面向 Zig 0.16.0 开发者的编程指南技能,聚焦该版本的语言与标准库变化。它汇总破坏性改动、迁移示例及本地文档查阅方法。
主要功能
- 01针对 Zig 0.16.0
- 02汇总语言级变更
- 03说明 std.Io 与标准库 API 更新
- 04提供旧 API 到新 API 的迁移示例
- 05指导查阅本地语言和标准库文档
适用场景
- 01将 Zig 项目迁移到 0.16.0
- 02核对标准库 API 的版本差异
- 03更新 I/O、网络或文件操作代码
- 04查找 Zig 0.16.0 的入口与环境变量用法
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ zigcc/ skills --skill zig-0.16安装后在 Claude Code 中输入/zig-0.16确认已加载。
附带文件scripts/check-zig-version.sh
详细介绍
Zig 0.16.0 编程指南
版本范围:此技能固定使用 Zig 0.16.0(稳定版)。 发行说明:https://ziglang.org/download/0.16.0/release-notes.html
Zig 0.16.0 是一个重大版本,引入了作为统一 I/O 接口的 std.Io,移除了 @Type,弃用了 @cImport,并对构建系统的包管理进行了大幅调整。
优先查阅本地文档
运行 zig env 查找安装路径——切勿将路径硬编码。重要字段:.lib_dir、.std_dir、.version。
- 语言参考:
<lib_dir>/../doc/langref.html - 标准库源码:阅读
.std_dir下的文件以验证 API。 - 标准库文档:运行
zig std启动本地 HTTP 服务。
搜索网页之前,务必先查阅本地文档。
快速参考:主要破坏性变更
| 旧版(0.15.x) | 新版(0.16.0) |
|---|---|
@Type(.Int(...)) | @Int(.signed, bits) |
@Type(.Struct(...)) | @Struct(layout, BackingInt, field_names, field_types, field_defaults, field_is_comptime, field_alignments) |
@Type(.Pointer(...)) | @Pointer(size, attrs, Element, sentinel) |
@Type(.Fn(...)) | @Fn(param_types, param_attrs, ReturnType, attrs) |
@Type(.Tuple(...)) | @Tuple(field_types) |
@cImport({...}) | b.addTranslateC(...) + @import("c")(已弃用) |
std.net | std.Io.net |
std.ArrayList.init(allocator) | std.ArrayList.initCapacity(allocator, n) |
std.crypto.random | std.Io.randomSecure(io, buf) |
std.meta.intToEnum | std.enums.fromInt |
std.fmt.FormatOptions | std.fmt.Options |
std.fmt.bufPrintZ | std.fmt.bufPrintSentinel |
std.io.fixedBufferStream | std.Io.Writer.fixed(buf) |
error.RenameAcrossMountPoints | error.CrossDevice |
error.NotSameFileSystem | error.CrossDevice |
error.SharingViolation | error.FileBusy |
error.EnvironmentVariableNotFound | error.EnvironmentVariableMissing |
std.Build.Step.ConfigHeader 的 cmake 处理 | 修正了行首空白 |
语言变更
移除 @Type——改用独立内建函数
@Type 已移除。请替换为以下内建函数:
// Integer type
const MyInt = @Int(.signed, 32);
// Struct type
const MyStruct = @Struct(
.auto, // layout
null, // BackingInt (for packed)
&.{"x", "y"}, // field_names
&.{ i32, i32 }, // field_types
&.{ null, null },// field_defaults
&.{ false, false }, // field_is_comptime
&.{ null, null },// field_alignments
);
// Pointer type
const MyPtr = @Pointer(.one, .{
.alignment = 8,
.address_space = .generic,
.is_const = false,
.is_volatile = false,
}, u8, null);
// Function type
const MyFn = @Fn(&.{i32, i32}, &.{.{}, .{}}, i32, .{});
// Tuple type
const MyTuple = @Tuple(&.{ i32, bool });
// Enum literal type
const EnumLitType = @EnumLiteral();
弃用 @cImport——改用构建系统转换
@cImport 已弃用。请在 build.zig 中使用 addTranslateC:
// build.zig
const translate_c = b.addTranslateC(.{
.root_source_file = b.path("src/c.h"),
.target = target,
.optimize = optimize,
});
translate_c.linkSystemLibrary("glfw", .{});
const exe = b.addExecutable(.{
.name = "myapp",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.optimize = optimize,
.target = target,
.imports = &.{
.{ .name = "c", .module = translate_c.createModule() },
},
}),
});
然后在 Zig 中使用:const c = @import("c");
注意:C 转换器现在基于 arocc,而非 libclang。
Switch 改进
packed struct和packed union现在可用作 switch 分支项(按底层整数比较)。decl字面量和依赖结果类型的表达式(例如@enumFromInt)现在可用作 switch 分支项。- 所有分支现在都允许捕获联合标签,不再仅限于 inline 分支。
- 现在不能再丢弃所有 switch 分支捕获。
- 已移除
.awake原子操作顺序;请使用.monotonic/.seq_cst。
其他语言层面的注意事项
- 小整数类型可强制转换为浮点数——例如,现在允许
u8→f32。 - 禁止使用运行时向量索引——向量索引必须在编译时已知。
- 向量和数组不再支持内存中的强制转换——必须逐个显式转换元素。
- 不能从函数返回普通局部变量的地址——例如,
return &local;现在会更一致地被拒绝。 - 打包联合不得包含未使用的位——所有位模式都必须有效。
- 打包结构体/联合中禁止使用指针。
- 显式对齐的指针类型现在在类型系统中与自然对齐的指针类型不同。
- 简化了依赖循环规则——大多数代码仍可正常工作,但自引用的对齐查询现在会报错。
标准库:std.Io
网络
std.net已移除。请使用std.Io.net(类型:IpAddress、Stream、Server、UnixAddress)。- 高级辅助函数(如
tcpConnectToHost、Address.parseIp、Stream.connect)已移除。请使用Io虚函数表:io.vtable.netConnectIp、netRead、netWrite、netListenIp、netAccept。 - 原始文件描述符操作:请使用
std.os.linux.socket、accept4、connect、bind、listen、write、read、close。 posix.socket、posix.bind、posix.accept、posix.shutdown、posix.writev、posix.listen、posix.pipe2、posix.getrandom、std.net.has_unix_sockets已移除。- Epoll:直接使用
std.os.linux.epoll_create1/epoll_ctl/epoll_wait。 - 新增:
std.Io.net.Socket.createPair,用于测试中的 socketpair。
I/O 读取器/写入器
- 读取器/写入器接口位于
std.Io下。TLS 需要*std.Io.Reader/*std.Io.Writer(结构体字段为.interface)。请传递接口指针,而不是将interface()作为函数调用。 std.time.sleep已移除;请使用std.Io.sleep(io, Duration, Clock)。std.Io.GenericWriter、std.Io.AnyWriter、std.Io.null_writer、std.Io.CountingReader已移除。
时间 / 随机数 / 环境变量 / 退出
std.time.milliTimestamp已移除。请使用std.time.Timer或std.Io.Clock.now(clock, io),并比较Timestamp.nanoseconds。- 安全随机字节:使用
std.Io.randomSecure(io, buf);没有std.crypto.random或std.posix.getrandom便捷函数。 std.process.getEnvVarOwned已移除;请使用std.c.getenv并复制结果。std.posix.exit已移除;请使用std.process.exit。
TLS 客户端选项
std.crypto.tls.Client.Options需要entropy: *const [entropy_len]u8和realtime_now_seconds: i64。请使用std.Io.randomSecure填充熵,并通过Io.Clock.now(.real, io)/ns_per_s计算秒数。
MemoryPool API 变更
std.heap.MemoryPool(T).initCapacity(allocator, n)会返回该池。create/destroy现在需要分配器。不再有无参init()或无参deinit()。
格式化选项
std.fmt.Options取代了FormatOptions。std.fmt.format辅助函数已移除;请直接调用writer.print(写入器位于std.Io.Writer中)。std.fmt.Formatter更名为Alt。std.fmt.bufPrintZ更名为std.fmt.bufPrintSentinel。
随机数 / 加密
std.crypto.random已移除。请使用std.Io实例:const io = std.Io.Threaded.global_single_threaded.ioBasic(); io.random(&buf);。Ed25519.KeyPair.generate现在需要一个io: std.Io参数。
枚举转换
std.meta.intToEnum已移除。请使用std.enums.fromInt(EnumType, value)(返回?EnumType)。meta.declList已移除。
测试中的固定缓冲区写入器
std.io.fixedBufferStream已移除。要进行内存写入,请使用var w = std.Io.Writer.fixed(buf);,并通过std.Io.Writer.buffered(&w)读取字节。std.ArrayList不再提供.init(allocator)简写;请使用.initCapacity(allocator, n)。
集合
SegmentedList已移除。BitSet、EnumSet:将initEmpty、initFull替换为声明字面量(例如.{}、.{ .a = true })。- 新增:
Io.Dir.renamePreserve— 重命名时不替换目标。 std.Io.Dir.rename返回error.DirNotEmpty,而不是error.PathAlreadyExists。
错误集合重命名
// Old -> New
error.RenameAcrossMountPoints -> error.CrossDevice
error.NotSameFileSystem -> error.CrossDevice
error.SharingViolation -> error.FileBusy
error.EnvironmentVariableNotFound -> error.EnvironmentVariableMissing
数学
math.sign现在会返回能够容纳所有可能值的最小整数类型。
线程
Thread.Mutex.Recursive已移除。
压缩
lzma、lzma2、xz已更新为使用Io.Reader/Io.Writer接口。
动态库
DynLib已移除对 Windows 的支持。请直接使用LoadLibraryExW和GetProcAddress。
新的执行模型:Juicy Main
Zig 0.16.0 引入了 std.process.Init,作为首选的 main 参数。这是此版本中入口点方面最大的变更。
main 签名
// 1. Bare (no args/env available)
pub fn main() !void { }
// 2. Minimal (raw argv + environ only)
pub fn main(init: std.process.Init.Minimal) !void { }
// 3. Juicy Main (recommended)
pub fn main(init: std.process.Init) !void { }
std.process.Init 内容
pub const Init = struct {
minimal: Minimal,
arena: *std.heap.ArenaAllocator, // process-lifetime arena, threadsafe
gpa: Allocator, // general-purpose allocator
io: Io, // default Io implementation
environ_map: *Environ.Map, // env vars (not threadsafe)
preopens: Preopens, // WASI-style preopened files
pub const Minimal = struct {
environ: Environ,
args: Args,
};
};
示例
const std = @import("std");
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
const io = init.io;
try std.Io.File.stdout().writeStreamingAll(io, "Hello, world!\n");
const args = try init.minimal.args.toSlice(init.arena.allocator());
for (args, 0..) |arg, i| {
std.log.info("arg[{d}] = {s}", .{ i, arg });
}
std.log.info("{d} env vars", .{init.environ_map.count()});
}
经验法则:如果程序需要 I/O、内存分配或环境变量,请使用 std.process.Init。
环境变量和进程参数不再是全局状态
std.os.environ 和 std.process.args 不再作为全局状态存在。现在只能通过 main 的 init 参数访问。
环境变量
// Juicy Main
for (init.environ_map.keys(), init.environ_map.values()) |key, value| {
std.log.info("env: {s}={s}", .{ key, value });
}
// Minimal main
std.log.info("HOME: {?s}", .{init.environ.getPosix("HOME")});
std.log.info("EDITOR: {s}", .{ try init.environ.getAlloc(arena, "EDITOR") });
const map = try init.environ.createMap(arena);
CLI 参数
// Iterate lazily
var args = init.args.iterate();
while (args.next()) |arg| {
std.log.info("arg: {s}", .{arg});
}
// To slice (needs allocator)
const args = try init.args.toSlice(arena);
任何需要环境变量的函数都应将 *const process.Environ.Map 或 process.Environ 作为参数。
移除 Thread.Pool → 使用 std.Io
std.Thread.Pool 已移除。请替换为 std.Io.Group、std.Io.async 或 std.Io.concurrent。
迁移示例
// OLD (0.15)
fn doAllTheWork(pool: *std.Thread.Pool) void {
var wg: std.Thread.WaitGroup = .{};
pool.spawnWg(wg, doSomeWork, .{ pool, &wg, first_work_item });
wg.wait();
}
// NEW (0.16)
fn doAllTheWork(io: std.Io) void {
var g: std.Io.Group = .init;
errdefer g.cancel(io);
g.async(io, doSomeWork, .{ io, &g, first_work_item });
g.wait(io);
}
fn doSomeWork(io: std.Io, g: *std.Io.Group, foo: Foo) void {
foo.doTheThing();
for (foo.new_work_items) |new| {
g.async(io, doSomeWork, .{ io, g, new });
}
}
重要:从 Thread.Pool 迁移到 Io 时,请将 Thread.Mutex、Thread.Condition、Thread.ResetEvent 转换为 Io.Mutex、Io.Condition、Io.Event。
文件 I/O 和 fs API 变更
readFileAlloc / readToEndAlloc
// OLD
const contents = try std.fs.cwd().readFileAlloc(allocator, file_name, 1234);
// NEW
const contents = try std.Io.Dir.cwd().readFileAlloc(io, file_name, allocator, .limited(1234));
// Note: FileTooBig -> StreamTooLong when limit reached.
// OLD
const contents = try file.readToEndAlloc(allocator, 1234);
// NEW
var file_reader = file.reader(&.{});
const contents = try file_reader.interface.allocRemaining(allocator, .limited(1234));
setTimestamps
// OLD
try atomic_file.file_writer.file.setTimestamps(io, src_stat.atime, src_stat.mtime);
// NEW
try atomic_file.file_writer.file.setTimestamps(io, .{
.access_timestamp = .init(src_stat.atime),
.modify_timestamp = .init(src_stat.mtime),
});
Io.File.Stat.atime 现在是 ?Timestamp(例如,在 ZFS 上可能为 null)。
目录遍历
std.Io.Dir.walk仍然存在。- 新增:
std.Io.Dir.walkSelectively——可选择是否递归遍历各目录,避免冗余的打开/关闭系统调用。 Walker和SelectiveWalker均新增了leave()和depth()。
fs.path.relative 现在是纯函数
// OLD
const relative = try std.fs.path.relative(gpa, from, to);
// NEW
const cwd_path = try std.process.currentPathAlloc(io, gpa);
const relative = try std.fs.path.relative(gpa, cwd_path, environ_map, from, to);
fs.path Windows 变更
windowsParsePath/diskDesignator/diskDesignatorWindows→parsePath、parsePathWindows、parsePathPosix- 新增
getWin32PathType componentIterator/ComponentIterator.init不再可能失败
当前目录 API 更名
// OLD
std.process.getCwd(buffer)
std.process.getCwdAlloc(allocator)
// NEW
std.process.currentPath(io, buffer)
std.process.currentPathAlloc(io, allocator)
预打开文件与原子/临时文件
预打开文件
// OLD (WASI)
const wasi_preopens: std.fs.wasi.Preopens = try .preopensAlloc(arena);
// NEW
const preopens: std.process.Preopens = try .init(arena);
// Or simply use init.preopens from Juicy Main.
在非 WASI 目标上,Preopens 是 void——零开销抽象。
原子/临时文件
std.Io.File.Atomic 是用于原子写入文件和处理临时文件的新 API。
- Linux:在可能的情况下集成
O_TMPFILE。 - 新增:
std.Io.File.hardLink - 临时/随机文件名现在通过 Io 虚函数表生成,而不是通过
std.crypto.random生成。
内存和分配器变更
ArenaAllocator 现在无锁且线程安全
std.heap.ArenaAllocator 不再需要用 ThreadSafeAllocator 包装。单线程性能相近,在争用情况下性能更快(约 7 个线程)。
移除 ThreadSafe Allocator
std.heap.ThreadSafe 已移除;请直接使用 ArenaAllocator,或手动同步访问。
内存锁定/保护已移至 std.process
// OLD
try std.posix.mlock();
try std.posix.mlock2(slice, std.posix.MLOCK_ONFAULT);
try std.posix.mlockall(slice, std.posix.MCL_CURRENT | std.posix.MCL_FUTURE);
// NEW
try std.process.lockMemory(slice, .{});
try std.process.lockMemory(slice, .{ .on_fault = true });
try std.process.lockMemoryAll(.{ .current = true, .future = true });
mmap/mprotect 标志现在是类型安全的结构体,而不是按位 OR:
// OLD
std.posix.PROT.READ | std.posix.PROT.WRITE
// NEW
.{ .READ = true, .WRITE = true }
MemoryPool
std.heap.MemoryPool(T).initCapacity(allocator, n)返回该池。create/destroy现在需要分配器参数。- 新增非托管变体:
MemoryPoolUnmanaged、MemoryPoolAlignedUnmanaged、MemoryPoolExtraUnmanaged。
Io.Writer.Allocating 对齐字段
alignment: std.mem.Alignment, // new field
容器和集合变更
迁移到“Unmanaged”
带有内嵌 Allocator 字段的托管变体正在逐步淘汰。
ArrayHashMap、AutoArrayHashMap、StringArrayHashMap已移除。AutoArrayHashMapUnmanaged→array_hash_map.AutoStringArrayHashMapUnmanaged→array_hash_map.StringArrayHashMapUnmanaged→array_hash_map.Custom
PriorityQueue
var queue = std.PriorityQueue(u32, void, lessThan).empty;
init→initContextadd→pushaddSlice→pushSliceremove/removeOrNull→popremoveIndex→popIndex
PriorityDequeue
var dq = std.PriorityDequeue(u32, void, lessThan).empty;
init→.emptyadd→pushremoveMinOrNull/removeMin→popMinremoveMaxOrNull/removeMax→popMaxremoveIndex→popIndex
BitSet / EnumSet
使用声明字面量,而不是 initEmpty/initFull:
var set = std.EnumSet(MyEnum).empty;
var full = std.EnumSet(MyEnum).full;
SegmentedList
std.SegmentedList已移除。
已移除的 API(快速列表)
| 已移除的 API | 替代方案 |
|---|---|
std.Thread.Pool | std.Io.Group、Io.async、Io.concurrent |
std.heap.ThreadSafe | std.heap.ArenaAllocator(现已线程安全) |
std.io.fixedBufferStream | std.Io.Reader.fixed(data) / std.Io.Writer.fixed(buf) |
std.Io.GenericReader | std.Io.Reader |
std.Io.AnyReader | std.Io.Reader |
std.Io.GenericWriter | std.Io.Writer |
std.Io.AnyWriter | std.Io.Writer |
std.Io.null_writer | 直接使用 std.Io.Writer |
std.Io.CountingReader | 没有直接替代方案;手动跟踪字节数 |
std.SegmentedList | 无 |
std.meta.declList | 无 |
std.fs.getAppDataDir | 无 |
std.posix.getCwd* | std.process.currentPath* |
std.posix.mlock* | std.process.lockMemory* |
std.posix.PROT_* 位标志 | 结构体字面量标志 |
std.ArrayHashMap | array_hash_map.Auto / .String / .Custom |
Zig 0.16.0 中的常用习惯用法
ArrayList
const std = @import("std");
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
var list = try std.ArrayList(u8).initCapacity(gpa, 16);
defer list.deinit(gpa);
try list.append(gpa, 'a');
try list.appendSlice(gpa, "hello");
try list.ensureTotalCapacity(gpa, 100);
const owned = try list.toOwnedSlice(gpa);
defer gpa.free(owned);
}
HashMap(默认/非托管风格)
var map = std.StringHashMap(u32).empty;
defer map.deinit(gpa);
try map.put(gpa, "key", 42);
const val = map.get("key") orelse 0;
使用 Juicy Main 的 stdout / stderr
pub fn main(init: std.process.Init) !void {
const io = init.io;
// Direct streaming write
try std.Io.File.stdout().writeStreamingAll(io, "Hello, world!\n");
// Or via writer interface
var stdout_writer = std.Io.File.stdout().writer(&.{});
try stdout_writer.interface.print("value: {d}\n", .{42});
}
固定缓冲区 Reader / Writer
// Reader from byte slice
var data = "line1\nline2\n";
var reader: std.Io.Reader = .fixed(data);
// Writer into a stack buffer
var buf: [256]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try writer.interface.print("count: {d}", .{7});
文件 I/O
// Read entire file
const contents = try std.Io.Dir.cwd().readFileAlloc(io, "input.txt", gpa, .limited(1024 * 1024));
defer gpa.free(contents);
// Write file atomically
var atomic = try std.Io.File.Atomic.init(io, gpa, "output.txt");
try atomic.file_writer.interface.print("data: {s}\n", .{"hello"});
try atomic.commit(io);
JSON
const MyStruct = struct {
name: []const u8,
value: u32,
};
// Parsing
const json_str =
\\{"name": "test", "value": 42}
;
const parsed = try std.json.parseFromSlice(MyStruct, gpa, json_str, .{});
defer parsed.deinit();
// Serialization to string (via Io.Writer.Allocating)
var out: std.Io.Writer.Allocating = .init(gpa);
defer out.deinit();
var stringify: std.json.Stringify = .{
.writer = &out.writer,
.options = .{},
};
try stringify.write(parsed.value);
const json_output = out.written();
类型反射(Comptime)
const info = @typeInfo(T);
if (info == .pointer) { ... }
if (info == .slice) { ... }
if (info == .@"struct") { ... }
if (info == .@"enum") { ... }
if (info == .@"union") { ... }
if (info == .optional) { ... }
测试
test "basic arithmetic" {
const std = @import("std");
try std.testing.expectEqual(4, 2 + 2);
try std.testing.expectError(error.OutOfMemory, mightFail());
}
test "no leaks" {
const std = @import("std");
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
const allocator = gpa.allocator();
const ptr = try allocator.create(u32);
defer allocator.destroy(ptr);
}
Leb128(二进制格式)
// OLD: std.leb.readUleb128(reader)
// NEW:
const value = try reader.takeLeb128(u64);
常见错误消息及修复方法(0.16)
| 错误 | 原因 | 修复方法 |
|---|---|---|
no field named 'getStdOut' | 使用了旧版 std.io.getStdOut() | 使用 std.Io.File.stdout() |
expected 2 argument(s), found 1 | ArrayList 方法缺少分配器参数 | 将分配器作为第一个参数添加 |
no member named 'Pool' in 'Thread' | std.Thread.Pool 已移除 | 使用 std.Io.Group + Io.async |
expected type '*const process.Environ.Map' | 函数需要环境映射参数 | 从 main(init) 传入 |
no member named 'fixedBufferStream' | 旧版 stream API 已移除 | 使用 std.Io.Reader.fixed(...) 或 std.Io.Writer.fixed(...) |
no field named 'getCwd' in 'process' | 已重命名 | 使用 std.process.currentPath* |
fingerprint field missing | 使用了旧版 build.zig.zon | 添加 .fingerprint = 0x...,并将 .name = "x" 改为 .name = .x |
type depends on itself for alignment | 对齐查询存在自引用 | 移除 @alignOf(@This()) 或重构 |
构建系统变更
build.zig.zon 中必须包含 Fingerprint
build.zig.zon 现在必须包含 fingerprint 字段,而且 name 必须是枚举字面量,而不是字符串:
.{
.name = .myproject, // enum literal, not "myproject"
.fingerprint = 0x123456789abcdef0,
.version = "0.1.0",
.dependencies = .{},
}
如果缺少这些内容,zig build 将会失败。
本地包覆盖:--fork
用于临时本地覆盖的新 CLI 标志:
zig build --fork=/path/to/local/fork
任何 name + fingerprint 匹配的依赖项都会解析到本地路径,而不是被获取。此设置是临时的——移除该标志即可恢复。
获取到 zig-pkg 目录的包
依赖项现在会获取到本地 zig-pkg/ 目录(与 build.zig 位于同一目录),而不是全局缓存。筛选后,它们会被重新压缩到全局缓存,以便日后复用。
单元测试超时
为每项测试指定超时时间:
zig build test --test-timeout 500ms
超时后,测试进程会被终止,并为下一项测试重新启动。
ConfigHeader
std.Build.Step.ConfigHeader现在可以正确处理 cmake 风格配置头文件开头的空白字符。
编译器/工具链
C 翻译
@cImport仍然存在,但已弃用,现在由 arocc 提供支持,而非 libclang。- 对于新代码,始终在
build.zig中使用addTranslateC。
类型解析
- 内部类型解析已重做。此前大多数可用代码仍然可用,部分“依赖循环”错误现已解决。
- 自引用对齐查询(例如
align(@alignOf(@This())))现在会正确报错。
LLVM 后端
- 实验性的增量编译支持。
- 错误集类型现在会在调试信息中降级为枚举,因此错误名称在运行时可见。
目标支持
重要新增内容:
aarch64-freebsd、aarch64-netbsd、loongarch64-linux、powerpc64le-linux、s390x-linux、x86_64-freebsd、x86_64-netbsd、x86_64-openbsd现已纳入 CI 测试。- 新增
aarch64-maccatalyst、x86_64-maccatalyst交叉编译支持。 - 初步支持
loongarch32-linux(暂不支持 libc)。 - 新增对 Alpha、KVX、MicroBlaze、OpenRISC、PA-RISC、SuperH 的基本支持。
- 移除 Solaris、AIX、z/OS 支持。
- 几乎所有主要目标平台的堆栈跟踪均已改进。
测试调整
- 对于进程内客户端/服务端测试,请使用
std.Io.net.Socket.createPair或原始socketpair(AF.UNIX, SOCK.STREAM|CLOEXEC, 0, &fds),避免依赖 Io vtable 网络(未接线的 Threaded nets 会返回NetworkDown)。 - 许多
std.Io函数的错误集已收紧,请相应移除不可达分支。
移植策略(0.15 → 0.16)
- 将
@Type调用替换为相应的新内置函数。 - 将
@cImport替换为build.zig中的addTranslateC。 - 在
build.zig.zon中添加fingerprint并修正name。 - 在应用中贯穿传递
std.Io——任何执行 I/O、休眠、随机数生成或时间操作的函数都需要io参数。 - 将
std.net用法更新为std.Io.net或原始系统调用。 - 更新
ArrayList调用,显式传入分配器并使用initCapacity。 - 修正错误集名称(CrossDevice、FileBusy、EnvironmentVariableMissing、DirNotEmpty)。
- 运行
zig build test --test-timeout 500ms,尽早发现可能卡住的测试。
编写新的 Zig 0.16.0 代码或从 0.15.x 升级时,请使用此技能。