Skip to content

Zig 编译期计算教程

Zig 通过编译期参数实现泛型,这是编译期鸭子类型:

// 泛型最大值函数
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
// 使用:编译期指定类型
const bigger_float = max(f32, 3.14, 2.71);
const bigger_int = max(u64, 100, 200);

编译期参数的特点:

  1. 调用时:值必须在编译期已知
  2. 函数内:值在编译期已知
  3. 类型是一等公民:可以作为参数传递
// ❌ 错误:尝试传递运行时确定的类型
fn invalidExample(condition: bool) void {
// condition 是运行时值
const T = if (condition) f32 else u64;
const result = max(T, 1234, 5678); // 编译错误!
}

错误信息:

error: unable to resolve comptime value
const result = max(if (condition) f32 else u64, 1234, 5678);
// 声明编译期变量
comptime var x: i32 = 10;
// 所有对 x 的操作都必须在编译期完成
x += 5; // ✅ 编译期计算
// x += someRuntimeValue(); // ❌ 错误
// 编译期变量的值必须编译期已知
const y = x; // y 也是编译期已知
// 编译期生成查找表
const SineTable = blk: {
var table: [360]f32 = undefined;
comptime var i = 0;
// 编译期循环:生成所有值
inline while (i < 360) : (i += 1) {
const radians = @as(f32, @floatFromInt(i)) * (std.math.pi / 180.0);
table[i] = std.math.sin(radians);
}
break :blk table;
};
// 运行时使用预计算的表
fn fastSin(degrees: u16) f32 {
const index = @mod(degrees, 360);
return SineTable[index];
}
// 使用 comptime 块强制编译期计算
comptime {
// 块内所有内容都在编译期计算
const x = 10 + 20; // ✅ 编译期计算
const y = fibonacci(10); // ✅ 如果 fibonacci 能在编译期计算
// 以下会导致编译错误:
// const z = readFromFile("data.txt"); // ❌ 运行时操作
// exit(); // ❌ 外部函数
}

在 comptime 表达式中:

  1. 所有变量都是 comptime 变量
  2. 所有控制流在编译期求值
  3. 不能有运行时副作用
  4. 不能调用有全局副作用的函数
// 同一个函数,既可在编译期调用,也可在运行时调用
fn factorial(n: u32) u32 {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
test "混合调用" {
// 运行时调用
const runtime_result = factorial(5);
try expect(runtime_result == 120);
// 编译期调用
const compiletime_result = comptime factorial(5);
try expect(compiletime_result == 120);
// 甚至可以编译期验证
comptime {
if (factorial(5) != 120) {
@compileError("数学崩坏了!");
}
}
}
fn buggyFactorial(n: u32) u32 {
// 忘记基准条件!
return n * buggyFactorial(n - 1); // 无限递归
}
test "编译期递归错误" {
// 编译时会显示详细的调用栈
const result = comptime buggyFactorial(5);
_ = result;
}

错误输出:

error: overflow of integer type 'u32' with value '-1'
return n * buggyFactorial(n - 1);
~~~~~~~^~~~~~
note: called at comptime here (5 times)
note: called at comptime here
const result = comptime buggyFactorial(5);
^~~~~~~~~~~~~~~~~~~
test "大计算量编译期计算" {
// 增加编译期分支配额
@setEvalBranchQuota(10000);
// 现在可以进行更大的编译期计算
const big_result = comptime complexComputation();
_ = big_result;
}
fn complexComputation() u32 {
var sum: u32 = 0;
comptime var i = 0;
inline while (i < 1000) : (i += 1) {
sum += i;
}
return sum;
}
// 容器级所有表达式隐式是编译期表达式
const PRIMES = firstNPrimes(100); // 编译期计算
const SUM_OF_PRIMES = sum(PRIMES); // 编译期计算
fn firstNPrimes(comptime n: usize) [n]u32 {
var primes: [n]u32 = undefined;
var count: usize = 0;
var candidate: u32 = 2;
while (count < n) {
var is_prime = true;
for (primes[0..count]) |prime| {
if (candidate % prime == 0) {
is_prime = false;
break;
}
}
if (is_prime) {
primes[count] = candidate;
count += 1;
}
candidate += 1;
}
return primes;
}
fn sum(numbers: []const u32) u32 {
var total: u32 = 0;
for (numbers) |n| {
total += n;
}
return total;
}
// 编译期生成的配置
const Config = struct {
debug: bool,
max_connections: u32,
timeout_ms: u32,
};
// 根据编译模式生成不同配置
const CURRENT_CONFIG: Config = comptime blk: {
var config = Config{
.debug = false,
.max_connections = 100,
.timeout_ms = 5000,
};
if (@import("builtin").mode == .Debug) {
config.debug = true;
config.timeout_ms = 10000; // 调试模式下更长超时
}
break :blk config;
};
// 泛型列表
fn List(comptime T: type) type {
return struct {
items: []T,
len: usize,
pub fn init(allocator: Allocator, capacity: usize) !@This() {
const items = try allocator.alloc(T, capacity);
return @This(){
.items = items,
.len = 0,
};
}
pub fn append(self: *@This(), value: T) !void {
if (self.len >= self.items.len) {
// 重新分配...
}
self.items[self.len] = value;
self.len += 1;
}
};
}
// 使用泛型
var int_list = try List(i32).init(allocator, 10);
defer allocator.free(int_list.items);
try int_list.append(42);
try int_list.append(100);
// 链表节点
const Node = struct {
next: ?*Node, // ✅ 允许:指针大小已知
data: i32,
pub fn create(data: i32) *Node {
const node = allocator.create(Node) catch unreachable;
node.* = Node{
.next = null,
.data = data,
};
return node;
}
};
// 二叉树节点
const BinaryTree = struct {
const Self = @This();
left: ?*Self,
right: ?*Self,
value: i32,
pub fn insert(self: *Self, value: i32) void {
if (value < self.value) {
if (self.left) |left| {
left.insert(value);
} else {
self.left = BinaryTree.create(value);
}
} else {
if (self.right) |right| {
right.insert(value);
} else {
self.right = BinaryTree.create(value);
}
}
}
};

7. 编译期元编程:实现 print 函数

Section titled “7. 编译期元编程:实现 print 函数”
const Writer = struct {
pub fn print(self: *Writer, comptime format: []const u8, args: anytype) !void {
comptime var state = .Start;
comptime var arg_index: usize = 0;
comptime var start: usize = 0;
// 内联循环:编译期遍历格式字符串
inline for (format, 0..) |c, i| {
switch (state) {
.Start => switch (c) {
'{' => {
// 输出普通文本
if (start < i) try self.write(format[start..i]);
state = .OpenBrace;
},
'}' => state = .CloseBrace,
else => {},
},
.OpenBrace => switch (c) {
'{' => { // 转义的 {
state = .Start;
start = i;
},
'}' => { // 参数占位符 {}
// 获取并输出参数
const arg = args[arg_index];
arg_index += 1;
try self.printValue(arg);
state = .Start;
start = i + 1;
},
's' => { // 字符串格式符 {s}
// 跳过 's',等待 '}'
},
else => @compileError("未知格式字符: " ++ [1]u8{c}),
},
.CloseBrace => switch (c) {
'}' => { // 转义的 }
state = .Start;
start = i;
},
else => @compileError("单独的 '}' 在格式字符串中"),
},
}
}
// 编译期检查
comptime {
if (args.len != arg_index) {
@compileError("未使用的参数");
}
}
// 输出剩余文本
if (start < format.len) {
try self.write(format[start..]);
}
}
fn write(self: *Writer, text: []const u8) !void {
// 实际写入实现...
}
pub fn printValue(self: *Writer, value: anytype) !void {
const T = @TypeOf(value);
switch (@typeInfo(T)) {
.Int => try self.writeInt(value),
.Float => try self.writeFloat(value),
.Pointer => |ptr_info| {
if (ptr_info.size == .Slice and ptr_info.child == u8) {
try self.write(value); // 字符串
} else {
@compileError("无法打印类型: " ++ @typeName(T));
}
},
else => @compileError("无法打印类型: " ++ @typeName(T)),
}
}
};

当调用 print("Hello {s}, count: {}", .{"World", 42}) 时,编译器会生成:

// 编译期生成的专用函数
fn print_specialized(self: *Writer) !void {
try self.write("Hello ");
try self.write("World"); // 字符串参数
try self.write(", count: ");
try self.writeInt(42); // 整数参数
}
// 自动生成类型信息
fn TypeInfo(comptime T: type) type {
return struct {
pub const name = @typeName(T);
pub const size = @sizeOf(T);
pub const alignment = @alignOf(T);
pub fn fields() []const FieldInfo {
comptime {
const info = @typeInfo(T);
if (info != .Struct) @compileError("不是结构体");
var fields_array: [info.Struct.fields.len]FieldInfo = undefined;
for (info.Struct.fields, 0..) |field, i| {
fields_array[i] = FieldInfo{
.name = field.name,
.type = field.type,
.offset = @offsetOf(T, field.name),
};
}
return &fields_array;
}
}
};
}
// 自动序列化
fn serialize(comptime T: type, value: T) []const u8 {
comptime {
var buffer: [1000]u8 = undefined;
var writer = std.io.fixedBufferStream(&buffer).writer();
const info = TypeInfo(T);
writer.print("{{ type: \"{s}\", fields: [", .{info.name}) catch unreachable;
inline for (info.fields()) |field| {
const field_value = @field(value, field.name);
writer.print("{{ \"{s}\": ", .{field.name}) catch unreachable;
serializeValue(field.type, field_value, writer) catch unreachable;
writer.writeAll(" }, ") catch unreachable;
}
writer.writeAll("] }") catch unreachable;
return writer.context.getWritten();
}
}
// API 版本兼容性检查
const ApiVersion = enum { V1, V2, V3 };
fn checkApiCompatibility(comptime Required: ApiVersion) void {
const Current = ApiVersion.V2; // 当前实现的版本
comptime {
if (@intFromEnum(Required) > @intFromEnum(Current)) {
@compileError("需要 API 版本 " ++ @tagName(Required) ++
",但当前是 " ++ @tagName(Current));
}
}
}
// 使用:编译期检查
fn useFeature() void {
checkApiCompatibility(.V2); // ✅ 通过
// checkApiCompatibility(.V3); // ❌ 编译错误
}
// 配置生成器
fn buildConfig(comptime options: anytype) type {
return struct {
// 默认值
pub const debug_mode = options.debug_mode orelse false;
pub const log_level = options.log_level orelse .Info;
pub const max_connections = options.max_connections orelse 100;
// 验证配置
comptime {
if (max_connections == 0) {
@compileError("max_connections 不能为 0");
}
if (debug_mode and log_level == .None) {
@compileError("调试模式下不能禁用日志");
}
}
// 生成运行时配置
pub fn createRuntimeConfig() RuntimeConfig {
return .{
.debug = debug_mode,
.log_level = @intFromEnum(log_level),
.max_conn = max_connections,
};
}
};
}
// 使用
const MyConfig = buildConfig(.{
.debug_mode = true,
.max_connections = 200,
});
const config = MyConfig.createRuntimeConfig();
fn sql(comptime query: []const u8, args: anytype) Query {
comptime {
// 分析查询,提取参数占位符
var param_count: usize = 0;
for (query) |c| {
if (c == '?') param_count += 1;
}
if (param_count != args.len) {
@compileError("参数数量不匹配");
}
// 生成参数类型检查
// ...
}
return Query{
.text = query,
.params = args,
};
}
// 使用:编译期验证
const query = sql("SELECT * FROM users WHERE age > ? AND name = ?", .{18, "Alice"});
// 编译期检查参数类型和数量
// 编译期注册路由
const Router = struct {
routes: std.StringHashMap(fn () void),
pub fn init(allocator: Allocator) !@This() {
var self = @This(){
.routes = std.StringHashMap(fn () void).init(allocator),
};
// 编译期发现并注册所有路由
comptime {
inline for (@typeInfo(@This()).Struct.decls) |decl| {
if (std.mem.startsWith(u8, decl.name, "route_")) {
const handler = @field(@This(), decl.name);
const path = decl.name["route_".len..];
// 这里实际上需要运行时注册
// 但路径和处理器在编译期已知
}
}
}
return self;
}
fn route_home() void {
std.debug.print("主页\n", .{});
}
fn route_about() void {
std.debug.print("关于\n", .{});
}
};
  1. 合理使用编译期计算:

    • 常量计算:配置文件、查找表
    • 类型检查:API 兼容性、参数验证
    • 代码生成:序列化、路由、查询构建
  2. 编译期错误处理:

    • 使用 @compileError 提供有用的错误信息
    • 利用编译期堆栈跟踪调试
    • 设置适当的分支配额
  3. 性能考虑:

    • 编译期计算增加编译时间
    • 合理平衡编译期和运行时计算
    • 避免过度复杂的编译期逻辑
  4. 代码组织:

    • 明确区分编译期和运行时逻辑
    • 使用 comptime 参数实现泛型
    • 利用编译期反射减少样板代码

Zig 的编译期计算能力让你可以在不引入宏语言或预处理器的前提下,实现强大的元编程功能。这是 Zig 语言简洁性和强大性的关键所在。