Chapter 56Builtins Quick Reference

附录B. 内置函数快速参考

概述

@builtins是编译器的动词;它们描述了Zig如何思考类型、指针和程序结构,并且它们在每个文件中都可用,无需导入。在第三部分尝试了编译时编程之后,本附录捕获了最常见的内置函数、它们的意图,以及在阅读或编写元编程重的Zig时应该记住的表面层契约。15

0.15.2版本稳定了几个内省辅助函数(@typeInfo@hasDecl@field)并澄清了新整数大小的截断语义,使得依赖这里总结的行为变得实用。v0.15.2

学习目标

  • 在扫描代码库时发现反射内置函数、算术辅助函数和控制内置函数之间的差异。
  • 结合类型检查内置函数来构建与用户提供类型一起工作的适配器。
  • 在范围和安全模式的边缘验证数值转换的运行时行为。

核心反射内置函数

反射内置函数为我们提供关于用户类型的结构化信息,而不获取原始指针或丢弃安全检查。15 下面的示例显示如何形成任何struct的文档化摘要,包括comptime字段、可选有效负载和嵌套数组。

Zig
//! Summarizes struct metadata using @typeInfo and @field.
const std = @import("std");

fn describeStruct(comptime T: type, writer: anytype) !void {
    const info = @typeInfo(T);
    switch (info) {
        .@"struct" => |struct_info| {
            try writer.print("struct {s} has {d} fields", .{ @typeName(T), struct_info.fields.len });
            inline for (struct_info.fields, 0..) |field, index| {
                try writer.print("\n  {d}: {s} : {s}", .{ index, field.name, @typeName(field.type) });
            }
        },
        else => try writer.writeAll("not a struct"),
    }
}

test "describe struct reports field metadata" {
    const Sample = struct {
        id: u32,
        value: ?f64,
    };

    var buffer: [256]u8 = undefined;
    var stream = std.io.fixedBufferStream(&buffer);
    try describeStruct(Sample, stream.writer());
    const summary = stream.getWritten();

    try std.testing.expect(std.mem.containsAtLeast(u8, summary, 1, "id"));
    try std.testing.expect(std.mem.containsAtLeast(u8, summary, 1, "value"));
}

test "describe struct rejects non-struct types" {
    var buffer: [32]u8 = undefined;
    var stream = std.io.fixedBufferStream(&buffer);
    try describeStruct(u8, stream.writer());
    const summary = stream.getWritten();
    try std.testing.expectEqualStrings("not a struct", summary);
}
运行
Shell
$ zig test 01_struct_introspection.zig
输出
Shell
All 2 tests passed.

在内联循环内使用@typeInfo加上@field,以便编译器在特化后仍然优化掉分支。17

值提取辅助函数

诸如@field@hasField@fieldParentPtr之类的内置函数让您可以将运行时数据映射回编译时声明,而不违反Zig的严格别名规则。以下片段显示如何在保持const正确性的同时暴露父指针。meta.zig

Zig
//! Demonstrates `@fieldParentPtr` to recover container pointers safely.
const std = @import("std");

const Node = struct {
    id: u32,
    payload: Payload,
};

const Payload = struct {
    node_ptr: *const Node,
    value: []const u8,
};

fn makeNode(id: u32, value: []const u8) Node {
    var node = Node{
        .id = id,
        .payload = undefined,
    };
    node.payload = Payload{
        .node_ptr = &node,
        .value = value,
    };
    return node;
}

test "parent pointer recovers owning node" {
    var node = makeNode(7, "ready");
    const parent: *const Node = @fieldParentPtr("payload", &node.payload);
    try std.testing.expectEqual(@as(u32, 7), parent.id);
}

test "field access respects const rules" {
    var node = makeNode(3, "go");
    const parent: *const Node = @fieldParentPtr("payload", &node.payload);
    try std.testing.expectEqualStrings("go", parent.payload.value);
}
运行
Shell
$ zig test 02_parent_ptr_lookup.zig
输出
Shell
All 2 tests passed.

@fieldParentPtr假设子指针有效且正确对齐;在调试构建中将其与std.debug.assert结合使用,以尽早捕获意外误用。37

数值安全内置函数

数值转换是未定义行为经常隐藏的地方;Zig通过@intCast@intFromFloat@truncate使截断显式化,这些都遵循安全模式语义。37 0.15.2改进了这些内置函数在溢出时发出的诊断,使它们在调试构建中成为可靠的守护者。

Zig
//! Exercises numeric conversion builtins with guarded tests.
const std = @import("std");

fn toU8Lossy(value: u16) u8 {
    return @truncate(value);
}

fn toI32(value: f64) i32 {
    return @intFromFloat(value);
}

fn widenU16(value: u8) u16 {
    return @intCast(value);
}

test "truncate discards high bits" {
    try std.testing.expectEqual(@as(u8, 0x34), toU8Lossy(0x1234));
}

test "intFromFloat matches floor for positive range" {
    try std.testing.expectEqual(@as(i32, 42), toI32(42.9));
}
test "intCast widens without loss" {
    try std.testing.expectEqual(@as(u16, 255), widenU16(255));
}
运行
Shell
$ zig test 03_numeric_conversions.zig
输出
Shell
All 3 tests passed.

将有损转换包装在小辅助函数中,以便意图保持可读,并且您可以在共享数字逻辑周围集中断言。10

编译时控制和守护

@compileError@panic@setEvalBranchQuota@inComptime让您直接控制编译时执行;它们是保持元编程确定性和透明的安全阀。15 下面的小示例在编译时守护向量宽度,并在分析期间计算小斐波那契数之前提高评估分支配额。

Zig
//! Demonstrates compile-time guards using @compileError and @setEvalBranchQuota.
const std = @import("std");

fn ensureVectorLength(comptime len: usize) type {
    if (len < 2) {
        @compileError("invalid vector length; expected at least 2 lanes");
    }
    return @Vector(len, u8);
}

fn boundedFib(comptime quota: u32, comptime n: u32) u64 {
    @setEvalBranchQuota(quota);
    return comptimeFib(n);
}

fn comptimeFib(comptime n: u32) u64 {
    if (n <= 1) return n;
    return comptimeFib(n - 1) + comptimeFib(n - 2);
}

test "guard accepts valid size" {
    const Vec = ensureVectorLength(4);
    const info = @typeInfo(Vec);
    try std.testing.expectEqual(@as(usize, 4), info.vector.len);
    // Uncommenting the next line triggers the compile-time guard:
    // const invalid = ensureVectorLength(1);
}

test "branch quota enables deeper recursion" {
    const result = comptime boundedFib(1024, 12);
    try std.testing.expectEqual(@as(u64, 144), result);
}
运行
Shell
$ zig test 04_comptime_guards.zig
输出
Shell
All 2 tests passed.

@compileError立即停止编译单元;谨慎使用它,并倾向于在运行时验证更便宜时返回错误。留下一个注释掉的调用(如示例中)来记录失败模式而不破坏构建。12

交叉检查模式

  • 在使用用户类型的可选功能之前,使用@hasDecl@hasField驱动重构;这与第17章引入的防御风格相匹配。
  • 结合@TypeOf@typeInfo@fieldParentPtr以在验证代码中保持诊断清晰——三元组使在不变式失败时打印结构信息变得容易。
  • 记住一些内置函数(如@This)依赖于词法范围;重新组织文件可能会静默改变它们的含义,因此在每次重大重新排列后重新运行测试。36

注意事项和警告

  • 与分配器交互的内置函数(@alignCast@ptrCast)仍然遵循Zig的别名规则;当有疑问时依赖std.mem辅助函数。3
  • @setEvalBranchQuota对当前编译时执行上下文是全局的;保持配额狭窄以避免掩盖无限递归。15
  • 一些实验性内置函数出现在夜间构建中但不在0.15.2中——在使用新名称之前固定您的工具。

练习

  • 构建一个诊断辅助函数,使用@typeInfo.union打印任何联合体的标记名称。17
  • 扩展数值转换示例,以发出截断前后位模式之间的人类可读差异。fmt.zig
  • 编写一个编译时守护,拒绝缺少name字段的struct,然后将其集成到通用格式化器管道中。36

替代方案和边界情况

  • 当内置函数复制现有行为时,优先使用更高级别的std辅助函数——标准库通常为您包装边界情况。43
  • 对匿名struct的反射可以产生编译器生成的名称;如果面向用户的日志需要稳定性,请在您自己的元数据中缓存它们。12
  • 在与C交互时,请记住一些内置函数(例如@ptrCast)可能影响调用约定;在部署前仔细检查ABI部分。33

Help make this chapter better.

Found a typo, rough edge, or missing explanation? Open an issue or propose a small improvement on GitHub.