mcpField
Z
zig-0-17

zig-0-17

github.com/zigcc开发5345 次安装3 周前更新
代码仓库

关于 zig-0-17

zig-0-17 是面向 Zig 0.17.0 开发者的编程指南,重点介绍该版本的语言和标准库变化。指南提供迁移示例,并建议优先查阅本地安装中的文档与源码。

主要功能

  1. 01Zig 0.17.0 变更参考
  2. 02破坏性变更迁移示例
  3. 03内置功能与语法更新
  4. 04标准库 API 变化
  5. 05优先查阅本地文档

适用场景

  1. 01迁移 Zig 0.16.x 代码
  2. 02查询更新后的内置功能用法
  3. 03调整标准库调用
  4. 04依据本地文档核对 API

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/zigcc/skills --skill zig-0-17

安装后在 Claude Code 中输入/zig-0-17确认已加载。

附带文件scripts/check-zig-version.sh

详细介绍

Zig 0.17.0 编程指南

版本范围:固定为 Zig 0.17.0(稳定版)。 发行说明:https://ziglang.org/download/0.17.0/release-notes.html

Zig 0.17.0 进一步提升了语言的稳定性,朝着 1.0 迈进。主要亮点包括:用 @backingInt/@fromBackingInt 替换 @intFromEnum/@enumFromInt;将类型反射(std.lang.Type)和容器内置函数改为结构体数组(SoA)形式;移除数组乘法语法 **,改用 @splat;用线程安全的 SafeAllocator 替换 DebugAllocator;将 fmt.allocPrint 移入 mem.Allocator;将 C 转译解耦为外部包;以及将构建系统拆分为独立的 Configurer 和 Maker 进程,并通过细粒度跟踪缓存污染。


优先查阅本地文档

运行 zig env 查找本地安装路径——切勿将路径硬编码:

  • 语言参考:<lib_dir>/../doc/langref.html
  • 标准库源代码:检查 .std_dir 下的源文件以获取准确签名。
  • 标准库文档:运行 zig std 启动本地文档 HTTP 服务。

在线搜索前,务必先查阅本地文档或源代码。


快速参考:主要不兼容变更

旧版(0.16.x)新版(0.17.0)说明
@intFromEnum(val)@backingInt(val)也适用于位包和带标签联合体
@enumFromInt(val)@fromBackingInt(val)返回类型由推断得出;会对非法值进行安全检查
[1]T{val} ** len@splat(val)已移除数组乘法 **;请使用 @splat
@bitCast(extern_struct_val)@ptrCast(&val).* 或 extern union禁止对 extern struct/extern union 使用 @bitCast
void{}{}已移除 void{} 语法
errdefer |err| { ... }重构为辅助函数 + catch |err|已移除 errdefer 中的错误捕获
i0u0已移除 i0 原始整数类型
@hasDecl(在同一文件中对私有声明返回 true)仅对 pub 声明返回 true现在在所有文件中行为一致
@cImport({...})b.dependency("translate_c", .{}) + Translator已移除 @cImport;C 转译器已移至独立包
std.heap.DebugAllocatorstd.heap.SafeAllocator线程安全、不复用内存,并可检测泄漏和损坏
std.heap.StackFallbackAllocator(N)std.heap.BufferFirstAllocator.init(buf, fallback)显式传入缓冲区,而非编译时大小
std.fmt.allocPrint(arena, ...)arena.print(...) / allocator.print(...)已移入 std.mem.Allocator
list.getLastOrNull()list.last()返回 ?T
list.getLast()list.last().?通过 .? 断言非空;新增的 list.lastPtr() 返回 ?*T
std.zon.parse.fromSliceAllocstd.zon.parse.fromSlice(T, .{ ... })使用结构体参数;新增 updateFromSlice
std.bit_set.IntegerBitSetstd.bit_set.Integer同理:Array、Static、Dynamic;使用 .empty/.full
@typeInfo(S).@"struct".fieldsfield_names、field_types、field_attrs类型反射已重写为结构体数组(SoA)形式
@Struct(7 个参数)@Struct(layout, BackingInt, names, types, attrs)5 个参数;@Union(5 个参数),@Enum(4 个参数)
std.lang.OptimizeModestd.lang.Optimize标签改为小写:.debug、.safe、.fast、.small
@import("builtin").cpu / os / abi@import("builtin").target.cpu / os / abi通过 .target 访问
b.build_root(Directory)b.root(Cache.Path)路径表示形式已更改
if (b.args) |args| run.addArgs(args);run.addPassthruArgs()在参数变更时保持配置缓存纯净
b.findProgram(...)b.findProgramLazy(...)惰性解析可避免配置缓存污染

语言变更

1. @backingInt 和 @fromBackingInt 替代枚举转换内置函数

@intFromEnum 和 @enumFromInt 已弃用(#35966)。请使用 @backingInt 和 @fromBackingInt:

  • @backingInt 适用于:
    • 所有 enum 类型。
    • 具有显式后备整数的位打包类型(packed struct / packed union)。
    • 带标签的 union 类型(返回当前活动标签的后备整数)。
  • @fromBackingInt 会推断目标类型(任何枚举或具有显式后备整数的位打包类型)。它接受与该后备类型匹配的整数,并对枚举的标签值执行安全检查。
  • 空枚举必须将 noreturn 声明为其后备整数(例如,enum(noreturn) {})。
  • zig fmt 会自动升级旧的 @intFromEnum 和 @enumFromInt 调用。
const Status = enum(u8) {
    ok = 0,
    err = 1,
};

const status: Status = .ok;
// Convert enum to backing integer
const raw: u8 = @backingInt(status);

// Convert backing integer to enum (type is inferred from context)
const restored: Status = @fromBackingInt(raw);

// Helper function to query backing integer type:
const TagInt = std.meta.BackingInt(Status); // u8
2. @bitCast 语义:仅限逻辑位表示

Zig 0.17.0 重新定义了 @bitCast,使其重新解释值的逻辑位表示,因此完全不受字节序影响:

  • 允许的类型:void、bool、整数(comptime_int 除外)、浮点数(comptime_float 除外)、以整数为后备类型的 enum(T)、packed struct(T)、packed union(T),以及这些类型的数组/向量。
  • 禁止:不再允许将 extern struct 和 extern union 传递给 @bitCast。
  • 若要对 extern struct 的内存表示进行类型双关,请使用 @ptrCast 或 extern union:
const TwoBytes = extern struct {
    b0: u8,
    b1: u8,
};

test "type pun extern struct in 0.17" {
    const bytes: TwoBytes = .{ .b0 = 0x12, .b1 = 0xAB };

    // OLD (0.16): const int: u16 = @bitCast(bytes); // COMPILE ERROR in 0.17

    // NEW (0.17): Use pointer cast
    const int_ptr: *align(1) const u16 = @ptrCast(&bytes);
    const int: u16 = int_ptr.*;
    _ = int;
}
3. 已移除数组乘法 ** — 请使用 @splat

重复语法 [1]u8{0} ** N 已移除。请使用 @splat:

// OLD (0.16)
var buf = [1]u8{0} ** 1024;
const arr = [1]f32{1.5} ** 4;

// NEW (0.17)
var buf: [1024]u8 = @splat(0);
const arr: [4]f32 = @splat(1.5);
4. 新增 @divCeil

执行向正无穷大($+\infty$)方向舍入的整数除法,与 @divTrunc、@divFloor 和 @divExact 互为补充:

try std.testing.expectEqual(2, @divCeil(5, 3));
try std.testing.expectEqual(-1, @divCeil(-5, 3));
5. @hasDecl 仅检查公开(pub)声明

以前,如果在同一源文件中调用,@hasDecl(T, name) 对私有声明也会返回 true。现在,无论调用方处于何种上下文,它都只检查 pub 声明。

const Foo = struct {
    pub const public_val = 1;
    const private_val = 2;
};

// In 0.17:
@hasDecl(Foo, "public_val") == true;
@hasDecl(Foo, "private_val") == false; // Always false even within the same file
6. 编译时已知长度切片的解引用和数组强制转换

现在可以直接解引用编译时已知长度的切片,也可以将其强制转换为数组指针:

const slice: []const u16 = &.{ 1, 2, 3 };
const array: [3]u16 = slice.*;               // Valid in 0.17
const array_ptr: *const [3]u16 = slice;      // Valid in 0.17
7. 语法移除:void{}、errdefer |err|、i0
  • 已移除 void{}:将 void{} 替换为 {}。
  • 已移除 errdefer |err|:不再允许在 errdefer 中捕获错误。请重构为内部函数,或通过 catch |err| 处理:
    // OLD:
    // errdefer |err| std.log.err("failed: {s}", .{@errorName(err)});
    
    // NEW:
    fn run() void {
        runInner() catch |err| {
            std.log.err("failed: {s}", .{@errorName(err)});
        };
    }
    fn runInner() !void { ... }
    
  • 已移除 i0:将所有 i0 用法替换为 u0。
  • 全局链接清理:已从 std.lang.GlobalLinkage 中移除 internal 和 link_once。link_once 请改用 weak,internal 则请省略 @export。
8. 已移除 @cImport — 使用外部 translate_c 包

@cImport 已彻底移除。此外,std.Build.Step.TranslateC 已弃用,请改用官方外部包:

zig fetch --save git+https://codeberg.org/ziglang/translate-c

在 build.zig 中:

const std = @import("std");
const Translator = @import("translate_c").Translator;

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const translate_c_dep = b.dependency("translate_c", .{});
    const translator: Translator = .init(translate_c_dep, .{
        .c_source_file = b.path("src/c.h"),
        .target = target,
        .optimize = optimize,
    });
    translator.linkSystemLibrary("glfw3", .{});

    const exe = b.addExecutable(.{
        .name = "myapp",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "c", .module = translator.mod },
            },
        }),
    });
    b.installArtifact(exe);
}

标准库变更

1. std.heap.SafeAllocator(替代 DebugAllocator)

std.heap.DebugAllocator 已弃用。请使用 std.heap.SafeAllocator:

  • 线程安全:无需外部锁即可支持并发。
  • 安全保障:在 deinit() 时报告内存泄漏;无效释放、操作竞态和缓冲区覆盖会触发 panic 或通过 AllocFooter 校验和引发段错误。
  • 不复用内存:后备内存永不回收,以便捕获释放后使用错误。
const std = @import("std");

test "SafeAllocator usage" {
    var gpa: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{});
    defer {
        const leaks = gpa.deinit();
        if (leaks != 0) @panic("Memory leak detected!");
    }
    const allocator = gpa.allocator();

    const slice = try allocator.alloc(u32, 16);
    defer allocator.free(slice);
}
2. BufferFirstAllocator(重新设计的 StackFallbackAllocator)

StackFallbackAllocator(N) 已由 std.heap.BufferFirstAllocator 替代。请传入显式缓冲区切片,而不要声明编译时缓冲区大小:

var stack_buf: [256]u8 = undefined;
var bfa: std.heap.BufferFirstAllocator = .init(&stack_buf, gpa);
const allocator = bfa.allocator();

// Allocations small enough will use stack_buf; spills fallback to gpa.
3. mem.Allocator.print 替代 std.fmt.allocPrint

allocPrint 现在是 std.mem.Allocator 上的实例方法:

// OLD (0.16)
const str = try std.fmt.allocPrint(allocator, "{s}={d}", .{ key, val });

// NEW (0.17)
const str = try allocator.print("{s}={d}", .{ key, val });
// For null-terminated strings:
const zstr = try allocator.printSentinel("{s}={d}", .{ key, val }, 0);
4. ArrayList 更新:last()、last().?、lastPtr()
  • getLastOrNull() 已弃用 → 请使用 last()(返回 ?T)。
  • getLast() 已弃用 → 请使用 last().?。
  • 新增 lastPtr() → 返回 ?*T。
var list = try std.ArrayList(u32).initCapacity(allocator, 8);
defer list.deinit(allocator);

try list.append(allocator, 42);

if (list.last()) |val| {
    // val == 42
}
const non_null = list.last().?;

if (list.lastPtr()) |ptr| {
    ptr.* += 1;
}
5. std.zon.parse 重构

std.zon 现在接受配置结构体,并从 arena 分配:

const Config = struct {
    host: []const u8,
    port: u16,
};

var diag: std.zon.parse.Diagnostics = undefined;
const parsed = try std.zon.parse.fromSlice(Config, .{
    .gpa = gpa,
    .arena = arena,
    .source = zon_text,
    .diagnostics = &diag,
});

// For in-place field updates:
// std.zon.parse.updateFromSlice(&config, .{ ... });
6. std.bit_set 重命名

标准位集变体已现代化:

  • IntegerBitSet $\rightarrow$ std.bit_set.Integer
  • ArrayBitSet $\rightarrow$ std.bit_set.Array
  • StaticBitSet $\rightarrow$ std.bit_set.Static
  • DynamicBitSetUnmanaged $\rightarrow$ std.bit_set.Dynamic
  • DynamicBitSet(托管) $\rightarrow$ std.bit_set.DynamicManaged(已弃用)
  • initEmpty() / initFull() $\rightarrow$ 使用 .empty / .full 声明字面量。
7. 类型反射:std.lang.Type 的结构体数组(SoA)

std.lang.Type 中的类型检查字段已从结构体数组(fields: []Field)迁移为数组结构体(field_names、field_types、field_attrs):

fn printFieldNames(comptime T: type) void {
    const info = @typeInfo(T).@"struct";
    inline for (info.field_names, info.field_types, info.field_attrs) |name, field_type, attrs| {
        _ = field_type;
        _ = attrs;
        std.debug.print("Field: {s}\n", .{name});
    }
}
已更新的类型创建内置函数:
  • @Struct(layout, BackingInt, field_names, field_types, field_attrs)(5 个参数,而非 7 个)
  • @Union(layout, tag_type, field_names, field_types, field_attrs)(5 个参数)
  • @Enum(tag_type, mode, field_names, field_values)(4 个参数)

提示:使用 &@splat(.{}) 设置默认字段属性:

const MyStruct = @Struct(
    .auto,
    null,
    &.{"x", "y"},
    &.{i32, i32},
    &@splat(.{}),
);
8. std.lang.Optimize(小写标签)

std.lang.OptimizeMode 已重命名为 std.lang.Optimize:

  • 标签:.debug、.safe、.fast、.small
  • 使用 std.lang.Optimize.runtimeSafety(mode),而非 std.debug.runtime_safety。
9. 其他弃用与清理
  • @import("builtin"):.cpu、.os、.abi、.object_format 已弃用 $\rightarrow$ 改用 .target.cpu、.target.os、.target.abi、.target.ofmt。
  • std.builtin 已弃用,改用 std.lang。
  • std.DoublyLinkedList.pop $\rightarrow$ popLast。
  • std.gpu $\rightarrow$ std.spirv。
  • std.ascii.indexOfIgnoreCase* $\rightarrow$ std.ascii.findIgnoreCase*。
  • Uri 已与 net.HostName 解耦 $\rightarrow$ 使用 std.Io.net.HostName.fromUri(uri, &buf)。
  • mem.eql 和 mem.findDiff 不再对浮点切片进行指针相等短路处理(可正确处理 NaN)。

构建系统与工具链

1. Maker 进程与 Configurer 进程分离

zig build 将项目配置与构建执行拆分:

  • Configurer:将 build.zig 求值为表示构建图的二进制格式。
  • Maker:可复用的预编译二进制文件,用于对任务求值、处理包获取并执行步骤。
  • 加速后续运行,并为 IDE 和工具提供 Build Server Protocol(--listen=-) 支持。
2. 配置缓存污染

当 build.zig 中的逻辑产生缓存系统无法跟踪的副作用时,会污染配置缓存,导致下次运行时进行完整重新求值。

  • b.findProgram(...) 会在配置时立即搜索 PATH $\rightarrow$ 污染缓存。
  • 推荐:b.findProgramLazy(...) 返回一个 LazyPath $\rightarrow$ 纯操作,不会污染缓存。
  • 如需显式声明文件/目录依赖并保持缓存纯净:
    b.dependOnFileContents(path);
    b.dependOnFileMetadata(path);
    b.dependOnDirectoryContents(dir_path);
    b.dependOnDirectoryMetadata(dir_path);
    
  • 通过 CLI 标志覆盖:--cache-poison=pure(默认值)、poisoned、disallowed 或 ignored。
3. Run 步骤:透传参数

不要在 build.zig 中检查 b.args。请使用 addPassthruArgs():

// OLD (0.16)
if (b.args) |args| {
    run_cmd.addArgs(args);
}

// NEW (0.17)
run_cmd.addPassthruArgs();

通过 zig build run -- arg1 arg2 更改 CLI 参数将不再使配置缓存失效!

4. 系统库与 pkg-config

在 Zig 0.17 中,Module.linkSystemLibrary 默认使用 pkg-config:

module.linkSystemLibrary("foo", .{});

对于不附带 .pc 文件的平台提供库(例如 Windows 的 bcrypt 和 ws2_32),请显式禁用 pkg-config:

module.linkSystemLibrary("bcrypt", .{
    .use_pkg_config = .no,
});

这仍会按名称链接该库;只是阻止 Zig 调用 pkg-config。

5. 构建根目录与路径选项
  • b.build_root(Directory)$\rightarrow$ b.root(Cache.Path)。
  • Step.Options:区分文件与目录:
    • addOptionPath(name, path)(仅限文件)
    • addOptionPathDirectory(name, path)(仅限目录)
    • addOptionPathUntracked(name, path)(退出依赖跟踪)
  • b.pathList(&.{ "src", "test" }) 为 fmt 步骤生成 []const LazyPath:
    const fmt_step = b.addFmt(.{
        .paths = b.pathList(&.{ "src", "build.zig" }),
    });
    
  • b.dependencyLazy:可能返回 error.LazyDependencyNeeded。在 pub fn build(b: *std.Build) !void 中:
    const dep = try b.dependencyLazy("my_dep", .{});
    

Zig 0.17.0 中的常见惯用写法

标准项目入口(Juicy Main)
const std = @import("std");

pub fn main(init: std.process.Init) !void {
    const io = init.io;
    const gpa = init.gpa;
    const arena = init.arena.allocator();

    // Print to stdout
    try std.Io.File.stdout().writeStreamingAll(io, "Running on Zig 0.17.0!\n");

    // Dynamic formatted string
    const greeting = try gpa.print("Hello, {s}!", .{"World"});
    defer gpa.free(greeting);

    // Read CLI args
    const args = try init.minimal.args.toSlice(arena);
    for (args, 0..) |arg, i| {
        std.log.info("arg[{d}]: {s}", .{ i, arg });
    }
}
在测试中使用 SafeAllocator 进行内存管理
const std = @import("std");

test "data processing test" {
    var gpa: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{});
    defer {
        const leaks = gpa.deinit();
        std.debug.assert(leaks == 0);
    }
    const allocator = gpa.allocator();

    var list = try std.ArrayList(u32).initCapacity(allocator, 4);
    defer list.deinit(allocator);

    try list.append(allocator, 100);
    try std.testing.expectEqual(@as(u32, 100), list.last().?);
}

常见错误消息与修复方法(0.17)

错误消息根本原因解决方案
error: cannot @bitCast from 'extern struct'@bitCast 不允许使用 extern struct/extern union使用 @ptrCast(&val).* 或使用 extern union
error: expected 5 arguments, found 7 on @StructSoA 更新中 @Struct 的参数数量减少将默认值/对齐方式/comptime 分组到 field_attrs 切片中
error: no member named 'getLastOrNull' in 'ArrayList'方法已重命名使用 list.last()
error: no member named 'allocPrint' in 'std.fmt'函数已移至 mem.Allocator直接调用 allocator.print(...)
error: expected type 'std.lang.Optimize', found 'std.builtin.OptimizeMode'OptimizeMode 已重命名,且标签改为小写使用 std.lang.Optimize.debug / .safe / .fast / .small
error: no member named 'initEmpty' in 'std.bit_set'初始化器语法已更新使用 std.bit_set.Integer.empty 或声明字面量 .empty
error: 'errdefer' cannot capture error已移除 |err| 捕获语法将逻辑封装到辅助函数中,并使用 catch |err|
error: cannot multiply array types with '**'已移除数组乘法将 [1]T{val} ** N 替换为 @splat(val)

移植策略(0.16 → 0.17)

  1. 先运行 zig fmt: zig fmt 会自动将 @intFromEnum $\rightarrow$ @backingInt,并将 @enumFromInt $\rightarrow$ @fromBackingInt。
  2. 修复数组重复写法: 搜索并将数组乘法 ** 替换为 @splat(...)。
  3. 检查 @bitCast: 确保 @bitCast 未用于 extern struct 或 extern union。将它们改为对指针使用 @ptrCast,或重构为 extern union。
  4. 更新 fmt.allocPrint: 将 std.fmt.allocPrint(alloc, ...) 改为 alloc.print(...)。
  5. 更新 ArrayList 调用: 将 getLastOrNull() 替换为 last(),将 getLast() 替换为 last().?。
  6. 迁移构建系统:
    • 在 build.zig 中,将 run_cmd.addArgs(args) 条件语句替换为 run_cmd.addPassthruArgs()。
    • 如果路径仅在 make 时需要,则将 b.findProgram 替换为 b.findProgramLazy。
    • 将 b.build_root 替换为 b.root。
    • 使用 b.pathList(...) 更新格式化步骤的 paths。
    • 如果使用 C 翻译,在 build.zig.zon 中添加 translate_c 依赖,并使用 Translator。
  7. 验证测试: 运行 zig build test,确保编译无误,并使用 SafeAllocator 验证没有内存泄漏。

评论

支持 Markdown