Zig 为什么值得看

Zig 是一个定位为“更好的 C”的系统级语言:没有隐藏控制流、没有隐藏内存分配、没有宏、没有异常,同时提供了 C 无法想象的元编程能力——comptime。

它的思路是:既然编译器在编译时已经知道所有类型信息,那为什么不干脆让你在编译期执行普通 Zig 代码?

comptime 基础:编译期执行的普通代码

const std = @import("std");

// 编译期计算斐波那契,结果直接内联为常量
fn fib(comptime n: u64) u64 {
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}

pub fn main() void {
    // fib(20) 在编译期求值,运行时零成本
    std.debug.print("fib(20) = {}\n", .{fib(20)});
}

与普通常量表达式不同,Zig 的 comptime 代码就是普通函数体——可以有循环、分支、递归。

泛型:类型只是 comptime 的值

Zig 没有 <T> 语法。所谓泛型,就是“接收 type 作为参数的函数返回类型”:

fn Stack(comptime T: type) type {
    return struct {
        const Self = @This();

        items: []T = &.{},
        allocator: std.mem.Allocator,

        pub fn init(allocator: std.mem.Allocator) Self {
            return .{ .allocator = allocator };
        }

        pub fn push(self: *Self, v: T) !void {
            self.items = try self.allocator.realloc(self.items, self.items.len + 1);
            self.items[self.items.len - 1] = v;
        }

        pub fn pop(self: *Self) ?T {
            if (self.items.len == 0) return null;
            const v = self.items[self.items.len - 1];
            self.items = self.allocator.resize(self.items, self.items.len - 1) orelse self.items[0..self.items.len - 1];
            return v;
        }
    };
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    var s = Stack(i32).init(gpa.allocator());
    try s.push(42);
    std.debug.print("{}\n", .{s.pop().?});
}

Stack(i32) 是一次类型构造调用,Stack(f64) 会生成另一份完全独立、无装箱的代码——零成本抽象。

编译期反射:生成序列化代码

fn FieldNames(comptime T: type) []const []const u8 {
    const info = @typeInfo(T);
    if (info != .@"struct") @compileError("需要结构体类型");

    const fields = info.@"struct".fields;
    var names: [fields.len][]const u8 = undefined;
    inline for (fields, 0..) |f, i| {
        names[i] = f.name;
    }
    return &names;
}

const User = struct { id: u32, name: []const u8, active: bool };

pub fn main() void {
    for (FieldNames(User)) |n| {
        std.debug.print("{s} ", .{n});
    }
    // 输出:id name active
}

inline for 会在编译期展开循环,@typeInfo 提供完整类型反射。基于这些,可以在编译期生成 JSON 序列化、ORM 映射、CLI 参数解析,而运行时没有任何反射开销。

编译期断言与错误

const Config = struct {
    port: u16,

    comptime {
        // 编译期校验:不符合直接报错,无法进入运行时
        if (@sizeOf(Config) > 64) {
            @compileError("Config 结构体过大");
        }
    }
};

// 编译期测试
comptime {
    if (fib(10) != 55) @compileError("fib 实现有误");
}

这是 Zig 取代“断言 + 单元测试”的一种方式:能被静态验证的东西,就不该等到运行时。

与 C 的互操作

// 直接 include C 头文件,无需绑定层
const c = @cImport({
    @cInclude("stdio.h");
    @cInclude("sqlite3.h");
});

pub fn main() void {
    _ = c.printf("hello from C\n");
}
# Zig 甚至自带 C/C++ 编译器
zig cc -O2 -o app main.c
zig build-exe main.zig -lc

zig cc 是一个开箱即用的交叉编译工具链,一条命令就能产出任意目标平台的可执行文件,这也是 Zig 在基础设施领域迅速流行的原因之一。

需要注意的现实

  • 标准库与生态仍在演进,0.x 版本间有破坏性变更,生产项目需锁定版本。
  • 手动内存管理是常态:所有分配都要显式传入 allocator,写起来更啰嗦,但内存行为完全可预测。
  • 没有隐藏分配这一约束让异步、IO 的库设计仍不稳定,选型时需评估。

小结

Zig 的 comptime 把“元编程”从一门独立语言(模板、宏)简化成了“在编译期跑普通代码”。如果你写 C/C++、做嵌入式或高性能基础设施,Zig 值得投入一个周末认真体验。