Ziglings 笔记 80: 匿名结构体 (Anonymous Structs)

类型工厂

在 Ziglings 的第 80 个练习中,我们终于揭开了 Zig 泛型数据结构的神秘面纱。

在其他语言中,你可能习惯了 class List<T> { ... }。 在 Zig 中,泛型不是一种特殊的语法,而是普通的代码逻辑。既然函数可以返回整数、字符串,为什么不能返回一个 struct 类型呢?

挑战:制造圆圈

我们需要定义两个圆:

  1. circle1:存储 i32 类型的坐标和半径。
  2. circle2:存储 f32 类型的坐标和半径。

我们有一个通用的生成器函数 Circle(T)

解决方案

我们需要调用这个函数来获取类型,并立即初始化它:

const print = @import("std").debug.print;

// 这是一个“类型工厂”函数
// 输入:一个类型 T (comptime)
// 输出:一个具体的 struct 类型
fn Circle(comptime T: type) type {
    return struct {
        center_x: T,
        center_y: T,
        radius: T,
    };
}

pub fn main() void {
    // 1. 创建 i32 版本的圆
    // Circle(i32) 返回了一个类型。
    // 我们紧接着用 { ... } 初始化了这个类型的一个实例。
    const circle1 = Circle(i32){
        .center_x = 25,
        .center_y = 70,
        .radius = 15,
    };

    // 2. 创建 f32 版本的圆
    const circle2 = Circle(f32){
        .center_x = 25.234,
        .center_y = 70.999,
        .radius = 15.714,
    };

    // 打印类型名称
    // 编译器会自动为这些匿名结构体生成名字,通常格式为文件名.函数名(参数)
    // 例如: main.Circle(i32)
    print("[{s}: {},{},{}] ", .{
        stripFname(@typeName(@TypeOf(circle1))),
        circle1.center_x,
        circle1.center_y,
        circle1.radius,
    });

    print("[{s}: {d:.1},{d:.1},{d:.1}]\n", .{
        stripFname(@typeName(@TypeOf(circle2))),
        circle2.center_x,
        circle2.center_y,
        circle2.radius,
    });
}

fn stripFname(mytype: []const u8) []const u8 {
    // 硬编码切片,仅用于演示,实际项目中不要这样做
    return mytype[22..];
}

核心知识点总结

1. 结构体即表达式

在 Zig 中,struct { ... } 就像 1 + 1 一样是一个表达式。 你可以把它赋值给变量:

const Point = struct { x: u8, y: u8 };

你也可以直接返回它:

return struct { x: u8, y: u8 };

2. 泛型的本质

Zig 的泛型本质上就是 Type Function (类型函数)。 当我们调用 Circle(i32) 时,编译器在编译期执行这个函数,生成了一个包含 i32 字段的新结构体。因为是编译期执行,所以没有运行时开销。

3. 类型名称的奥秘

如果你打印 @typeName(Circle(i32)),你会发现 Zig 给它起了一个很有意义的名字,比如 exercises.080_anonymous_structs.Circle(i32)。这对于调试非常有帮助,让你一眼就能看出这是一个由哪个函数生成的泛型类型。


后续预告:我们已经掌握了匿名结构体。接下来的练习将进入 匿名列表 (Anonymous Lists),也就是我们常说的元组 (Tuples)。如果你想一次性返回多个值,或者创建一个包含不同类型元素的数组,匿名列表是你的好帮手。