Skip to content

Zig 零比特类型教程

零比特类型是指 @sizeOf 返回 0 的类型,它们只有一个可能的值,因此不需要任何比特来表示。

// 1. void - 空类型
const v: void = {};
// 2. 零宽度整数
const z1: u0 = 0; // 只能是 0
const z2: i0 = 0; // 只能是 0
// 3. 零长度数组
const empty_array: [0]u8 = .{};
const empty_vector: @Vector(0, u8) = .{};
// 4. 元素为零比特类型的数组
const void_array: [10]void = .{{}} ** 10;
// 5. 只有一个变体的枚举
const SingleEnum = enum { only };
const e: SingleEnum = .only;
// 6. 所有字段都是零比特类型的结构体
const EmptyStruct = struct {
a: void,
b: u0,
c: [0]u8,
};
// 7. 只有一个字段且为零比特类型的联合体
const SingleUnion = union(enum) {
only: void,
};
// void 表示"无值"
const nothing: void = {};
// 只有一个可能的值:{}
test "void characteristics" {
try expect(@sizeOf(void) == 0);
try expect(@typeInfo(void) == .Void);
// void 表达式可以直接忽略
doSomething(); // ✅ 不需要处理返回值
// 但非 void 表达式必须处理
const x = getNumber(); // ✅ 必须使用返回值
// getNumber(); // ❌ 编译错误:值被忽略
}
fn doSomething() void {
// 不需要 return 语句
// 隐式返回 {}
}
fn getNumber() i32 {
return 42;
}
test "explicitly ignoring values" {
// 使用 _ 明确忽略返回值
_ = getNumber(); // ✅ 明确忽略
_ = doSomething(); // ✅ 也可以(但不是必须的)
// _ 是特殊标识符,不能用作变量名
// var _ = 10; // ❌ 编译错误
}
const std = @import("std");
test "HashMap as Set using void" {
const Set = std.AutoHashMap(i32, void);
var set = Set.init(std.testing.allocator);
defer set.deinit();
// 插入元素(值只能是 {})
try set.put(42, {});
try set.put(100, {});
// 检查存在性
try expect(set.contains(42));
try expect(!set.contains(99));
// 删除元素
_ = set.remove(42);
try expect(!set.contains(42));
}

优势对比:

类型内存占用代码生成
HashMap(i32, void)较小(无值字段)无值操作代码
HashMap(i32, bool)较大(有值字段)需要值操作代码
test "zero-length arrays" {
// 创建零长度切片
const empty_slice: []const u8 = &[0]u8{};
// 安全遍历(不会执行循环体)
for (empty_slice) |byte| {
@panic("永远不会执行");
}
// 用作占位符
const Header = struct {
magic: u32,
reserved: [0]u8, // 明确表示"无保留字节"
data: []const u8,
};
}
test "zero-width integers" {
const AlwaysZero = u0;
const zero: AlwaysZero = 0;
// const one: AlwaysZero = 1; // ❌ 编译错误
// 在泛型代码中有用
const Counter = struct {
count: u0 = 0, // 永远为0的计数器
pub fn increment(self: *Counter) void {
// self.count += 1; // ❌ 编译错误
// 什么也不做
}
};
}
export fn process_data() void {
var a: void = {};
var b: void = {};
const c = a;
_ = c;
// 这些操作不会生成任何机器码
// 因为它们不执行实际工作
}

生成的汇编代码(x86_64):

process_data:
push %rbp ; 函数序言
mov %rsp, %rbp ;
pop %rbp ; 函数结语
retq ; 返回
; 注意:没有与 void 操作相关的代码!
test "void vs anyopaque" {
// void - 已知零大小
try expect(@sizeOf(void) == 0);
try expect(@alignOf(void) == 1);
// anyopaque - 未知非零大小
try expect(@sizeOf(anyopaque) > 0);
try expect(@alignOf(anyopaque) == 1);
// void 可以实例化
const v: void = {};
// anyopaque 不能直接实例化
// const o: anyopaque = {}; // ❌ 编译错误
}
const Mode = enum { Debug, Release };
const Logger = struct {
// 根据编译模式选择类型
const LogData = if (@import("builtin").mode == .Debug)
struct { timestamp: u64, message: []const u8 }
else
void;
data: LogData,
pub fn log(self: *Logger, msg: []const u8) void {
switch (@import("builtin").mode) {
.Debug => {
self.data.timestamp = std.time.timestamp();
self.data.message = msg;
},
.Release => {
// 发布模式下不记录数据
_ = self;
_ = msg;
},
}
}
};
const State = union(enum) {
idle: void,
connecting: struct { timeout: u32 },
connected: struct { socket: std.os.socket_t },
pub fn transition(self: *State, new_state: anytype) void {
self.* = switch (@TypeOf(new_state)) {
void => .idle,
u32 => .{ .connecting = .{ .timeout = new_state } },
std.os.socket_t => .{ .connected = .{ .socket = new_state } },
else => @compileError("Invalid state type"),
};
}
};
// 定义一组操作,某些操作可能不需要返回值
const Operations = struct {
const Self = @This();
// 不需要返回值的操作
pub fn reset(self: *Self) void {
_ = self;
// 重置内部状态
}
// 需要返回值的操作
pub fn get_status(self: *Self) Status {
_ = self;
return .ok;
}
// 可能失败的操作
pub fn validate(self: *Self) !void {
_ = self;
// 如果无效则返回错误
// 否则隐式返回 {}
}
};
fn Container(comptime T: type) type {
return struct {
data: if (@sizeOf(T) == 0) void else T,
count: usize,
pub fn add(self: *@This(), value: T) void {
if (@sizeOf(T) == 0) {
// 对于零比特类型,只增加计数
self.count += 1;
} else {
// 对于普通类型,存储数据
self.data = value;
self.count += 1;
}
}
};
}
test "generic container with zero-bit types" {
const VoidContainer = Container(void);
var vc: VoidContainer = .{ .count = 0 };
vc.add({}); // 只增加计数
const IntContainer = Container(i32);
var ic: IntContainer = .{ .data = 0, .count = 0 };
ic.add(42); // 存储数据并增加计数
}
const Assert = struct {
// 编译期断言:确保类型是零比特的
pub fn isZeroBit(comptime T: type) void {
comptime {
if (@sizeOf(T) != 0) {
@compileError("Type must be zero-bit");
}
}
}
// 使用:在泛型约束中
pub fn ZeroBitSet(comptime Key: type) type {
isZeroBit(Key); // 编译期检查
return struct {
// 实现细节...
};
}
};
const CompactStruct = struct {
flag1: bool,
flag2: bool,
// 不需要的字段用 void 填充,不占用空间
reserved1: void,
reserved2: void,
data: i32,
};
test "memory layout" {
// 即使有 void 字段,结构体大小仍合理
try expect(@sizeOf(CompactStruct) == @sizeOf(bool) * 2 + @sizeOf(i32));
// void 字段不占用空间!
}
// 返回 void 的函数可以被内联优化
inline fn no_op() void {
// 这个函数调用会被完全优化掉
}
// 即使调用多次,也不会增加代码大小
fn optimized_function() void {
no_op();
no_op();
no_op();
// 所有调用都会被移除
}
// ❌ 不要这样:过度使用 void
const OverEngineered = struct {
// 每个字段都是 void,为什么不直接用一个 void?
initialize: void,
validate: void,
process: void,
};
// ✅ 应该这样:合理使用
const SimpleAPI = struct {
// 只有真正不需要返回值的操作才用 void
pub fn flush(self: *@This()) void {
// 确实不需要返回值
}
pub fn read(self: *@This()) !Data {
// 需要返回值
}
};
test "optional void" {
// ?void 只有两个可能的值:null 和 {}
const maybe_nothing: ?void = null;
const nothing: ?void = {};
// 这在表示"是否有操作发生"时有用
const event_occurred: ?void = if (random()) {} else null;
if (event_occurred) |_| {
// 事件发生了(但不需要值)
}
}
const Database = struct {
// 只关心成功/失败,不关心返回值的操作
pub fn begin_transaction(self: *@This()) !void {
// 成功时返回 {}
// 失败时返回错误
}
pub fn commit(self: *@This()) !void {
// 同上
}
};
// 使用
try db.begin_transaction();
defer db.rollback() catch {}; // 忽略回滚错误
// ... 操作 ...
try db.commit(); // 只关心是否成功

零比特类型是 Zig 中一个强大但容易被忽视的特性:

  1. 零运行时开销:不生成任何机器码
  2. 内存高效:不占用存储空间
  3. 类型安全:仍然参与类型检查
  4. 泛型友好:可以特化泛型代码
  5. 意图明确:明确表示”无值”的概念

适用场景:

  • 集合实现(HashMap<K, void>)
  • 编译期占位符
  • 无返回值的操作
  • 类型级状态表示
  • 内存布局优化

通过合理使用零比特类型,你可以编写出更高效、更明确、更类型安全的 Zig 代码。