Skip to content

Zig 结构体教程

// 声明结构体类型
const Point = struct {
x: f32,
y: f32,
};
// 创建实例(使用匿名结构体字面量)
const p: Point = .{
.x = 0.12,
.y = 0.34,
};
// 或明确指定类型
const q = Point{ .x = 1.0, .y = 2.0 };
  • 字段顺序:编译器决定,不保证与定义一致
  • 大小和对齐:字段保证 ABI 对齐
  • 零字段结构体:允许且大小为 0
const Vec3 = struct {
x: f32,
y: f32,
z: f32,
// 构造方法
pub fn init(x: f32, y: f32, z: f32) Vec3 {
return Vec3{
.x = x,
.y = y,
.z = z,
};
}
// 实例方法(显式 self 参数)
pub fn dot(self: Vec3, other: Vec3) f32 {
return self.x * other.x + self.y * other.y + self.z * other.z;
}
// 静态方法
pub fn zero() Vec3 {
return Vec3.init(0, 0, 0);
}
};
test "methods" {
const v1 = Vec3.init(1.0, 0.0, 0.0);
const v2 = Vec3.init(0.0, 1.0, 0.0);
// 点语法调用
try expect(v1.dot(v2) == 0.0);
// 或作为命名空间函数调用
try expect(Vec3.dot(v1, v2) == 0.0);
}

结构体可以包含常量、变量、类型等声明:

const MathConstants = struct {
pub const PI = 3.141592653589793;
pub const E = 2.718281828459045;
var callCount: u32 = 0; // 容器级变量
pub fn increment() void {
callCount += 1;
}
};
test "struct declarations" {
try expect(MathConstants.PI > 3.0);
MathConstants.increment();
}
fn setYBasedOnX(x: *f32, y: f32) void {
const point: *Point = @fieldParentPtr("x", x);
point.y = y;
}
test "field parent pointer" {
var point = Point{ .x = 0.1, .y = 0.2 };
setYBasedOnX(&point.x, 0.3);
try expect(point.y == 0.3);
}
  • 当只有字段指针时需要访问整个结构体
  • 回调函数中获取上下文
  • 底层系统编程
fn LinkedList(comptime T: type) type {
return struct {
pub const Node = struct {
prev: ?*Node,
next: ?*Node,
data: T,
};
first: ?*Node,
last: ?*Node,
len: usize,
pub fn init() @This() {
return @This(){
.first = null,
.last = null,
.len = 0,
};
}
};
}
test "generic struct" {
const IntList = LinkedList(i32);
const list = IntList.init();
try expect(list.len == 0);
// 编译期函数会被记忆化
try expect(LinkedList(i32) == LinkedList(i32));
}
// 可以将类型赋值给变量
const ListType = LinkedList(f64);
const list: ListType = .{
.first = null,
.last = null,
.len = 0,
};
const Config = struct {
timeout: u32 = 5000, // 默认值 5000
retries: u8 = 3, // 默认值 3
debug: bool = false, // 默认值 false
};
test "default values" {
// 可以省略有默认值的字段
const config = Config{
.debug = true, // 只覆盖这个字段
};
try expect(config.timeout == 5000);
try expect(config.retries == 3);
try expect(config.debug == true);
}

不要在不满足数据不变量的结构体中使用默认值:

// ❌ 危险的例子
const Threshold = struct {
minimum: f32 = 0.25,
maximum: f32 = 0.75, // 必须 >= minimum
fn isValid(self: Threshold) bool {
return self.maximum >= self.minimum;
}
};
// 用户可能创建无效的实例
var bad = Threshold{ .maximum = 0.1 }; // 违反不变量!
const Threshold = struct {
minimum: f32,
maximum: f32, // 必须 >= minimum
// 提供命名默认值
pub const default: Threshold = .{
.minimum = 0.25,
.maximum = 0.75,
};
// 或提供构造函数
pub fn init(min: f32, max: f32) !Threshold {
if (max < min) return error.InvalidThreshold;
return Threshold{
.minimum = min,
.maximum = max,
};
}
};
// 匹配 C ABI 的内存布局
const CPoint = extern struct {
x: f32,
y: f32,
};
// 用于 FFI(外部函数接口)
extern "c" fn get_point() CPoint;
// 普通结构体更适合 Zig 内部使用
const ZigPoint = struct {
x: f32,
y: f32,
};

建议: 只在需要明确的内存布局时使用 extern struct。

压缩结构体基于整数位解释,有明确的内存布局:

const Color = packed struct {
r: u5, // 5位
g: u6, // 6位
b: u5, // 5位
a: u1, // 1位(alpha)
// 总共 17位,但实际使用 32位(下一个对齐边界)
};
const FlagSet = packed struct(u8) { // 显式指定 8位
flag1: bool, // 1位
flag2: bool, // 1位
flag3: bool, // 1位
_reserved: u5 = 0, // 5位保留
};
  • 整数:使用指定位宽
  • bool:使用 1 位
  • 枚举:使用其标签类型的位宽
  • 压缩联合体:使用最大字段的位宽
  • 压缩结构体:使用其基础整数
const Full = packed struct {
number: u16,
};
const Divided = packed struct {
half1: u8,
quarter3: u4,
quarter4: u4,
};
test "bitCast between packed structs" {
const full = Full{ .number = 0x1234 };
const divided: Divided = @bitCast(full);
// 小端序:0x1234 存储为 [0x34, 0x12]
try expect(divided.half1 == 0x34);
try expect(divided.quarter3 == 0x2); // 高4位中的 0x2
try expect(divided.quarter4 == 0x1); // 高4位中的 0x1
}
const BitField = packed struct {
a: u3,
b: u3,
c: u2,
};
test "non-byte-aligned field pointers" {
var field = BitField{ .a = 1, .b = 2, .c = 3 };
const ptr = &field.b; // ✅ 允许取地址
// 所有字段共享相同地址(都在同一字节内)
try expect(@intFromPtr(&field.a) == @intFromPtr(&field.b));
// 但类型不同(包含位偏移信息)
// &field.b 的类型是 *align(1:3:1) u3
}

限制: 非字节对齐指针不能传递给期望普通指针的函数。

test "offsets" {
comptime {
// 位偏移
try expect(@bitOffsetOf(BitField, "a") == 0);
try expect(@bitOffsetOf(BitField, "b") == 3);
try expect(@bitOffsetOf(BitField, "c") == 6);
// 字节偏移(都在第0字节)
try expect(@offsetOf(BitField, "a") == 0);
try expect(@offsetOf(BitField, "b") == 0);
try expect(@offsetOf(BitField, "c") == 0);
}
}
// 结构体整体对齐
const S = packed struct {
a: u32,
b: u32,
};
var foo: S align(4) = .{ .a = 1, .b = 2 };
// 字段单独对齐
const AlignedFields = struct {
a: u32 align(2), // 2字节对齐
b: u32 align(64), // 64字节对齐
};
// ✅ 正确做法:一次性写入整个寄存器
pub const GpioRegister = packed struct(u8) {
GPIO0: bool,
GPIO1: bool,
GPIO2: bool,
GPIO3: bool,
reserved: u4 = 0,
};
const gpio: *volatile GpioRegister = @ptrFromInt(0x0123);
pub fn setGpio(new_state: GpioRegister) void {
gpio.* = new_state; // 原子写入整个寄存器
}
// ❌ 错误做法:逐个字段设置(非原子)
fn badSetGpio() void {
gpio.GPIO0 = true; // 可能与其他字段写入冲突
gpio.GPIO1 = false;
}

Zig 为匿名结构体自动生成名称:

  1. 变量初始化:variable: struct_name.main.Foo
  2. 函数返回:function: struct_name.List(i32)
  3. 匿名:anonymous: struct_name.main__struct_22691
  4. 嵌套结构体:parent.child
pub fn main() void {
const Foo = struct {}; // 命名为 "struct_name.main.Foo"
const Bar = struct { // 外层结构体
const Inner = struct {}; // 命名为 "struct_name.main.Bar.Inner"
};
}
const Point = struct { x: i32, y: i32 };
// 类型推断
const pt: Point = .{ .x = 13, .y = 67 };
// 完全匿名结构体
const result = .{
.success = true,
.value = 42,
.message = "OK",
};

没有中间副本,直接实例化到目标位置:

fn createPoint() Point {
return .{ // 直接构造返回位置
.x = 10,
.y = 20,
};
}
// 元组:字段隐式命名为 "0", "1", "2"...
const tuple = .{ 1234, 12.34, true, "hi" };
test "tuple operations" {
// 访问(使用字符串化的数字)
try expect(tuple.@"0" == 1234);
try expect(tuple.@"1" == 12.34);
// 索引(编译期已知)
try expect(tuple[0] == 1234);
// 长度
try expect(tuple.len == 4);
// 连接和重复
const combined = tuple ++ .{false, true};
const repeated = .{"hello"} ** 3;
}
// 从块中返回多个值
const min, const max = blk: {
var min_val: i32 = std.math.maxInt(i32);
var max_val: i32 = std.math.minInt(i32);
// ... 计算
break :blk .{ min_val, max_val };
};
// 函数返回元组
fn divmod(a: i32, b: i32) struct { i32, i32 } {
return .{ a / b, a % b };
}
const quotient, const remainder = divmod(10, 3);
test "inline for with tuples" {
const values = .{ 1, 2, 3, 4, 5 };
var sum: i32 = 0;
inline for (values) |value| {
sum += value;
}
try expect(sum == 15);
}
const Query = struct {
select: []const u8 = "*",
from: []const u8,
where: ?[]const u8 = null,
limit: ?u32 = null,
pub fn build(self: Query) []const u8 {
// 构建 SQL 查询...
}
};
// 使用默认值和命名参数
const query = Query{
.from = "users",
.where = "age > 18",
.limit = 100,
};
const Connection = struct {
const Closed = struct { addr: []const u8 };
const Opening = struct { addr: []const u8 };
const Open = struct { fd: std.os.fd_t };
state: union(enum) {
closed: Closed,
opening: Opening,
open: Open,
},
pub fn connect(self: *Connection) void {
switch (self.state) {
.closed => |*closed| {
self.state = .{ .opening = .{ .addr = closed.addr } };
// 开始连接...
},
else => unreachable,
}
}
};
const Result = union(enum) {
success: struct { value: i32 },
failure: struct { error: []const u8, code: u32 },
pub fn unwrap(self: Result) i32 {
return switch (self) {
.success => |s| s.value,
.failure => |f| {
std.debug.print("Error: {s} (code: {})\n", .{f.error, f.code});
unreachable;
},
};
}
};
  1. 默认使用普通结构体,只在需要时用 extern 或 packed
  2. 避免危险的默认值,使用构造函数或命名默认值
  3. 合理使用方法,提高代码组织性
  4. 利用元组进行多值返回
  5. 注意压缩结构体的指针语义
  6. MMIO 使用一次性写入,避免字段级访问
  7. 结构体组合优于继承(Zig 没有继承)
  8. 使用 @This() 引用当前结构体类型

通过掌握 Zig 结构体的各种特性,你可以创建高效、类型安全的数据结构,充分利用编译器的优化能力。