如何学习 Zig:一条走得通的路线

Zig 的学习资源比大多数语言少,而且分散。官方文档还不完整,中文资料几乎没有,社区相对小。但 Zig 的语言本身其实不复杂——核心概念就那几个,搞清楚了学起来很快。

这篇文章的目标是给你一条具体的路线:从哪里开始、按什么顺序学、在哪里容易卡住、做什么项目练手。假设你有 Java/Go/Python 背景,没有 C 基础也没关系。

一、学之前:你需要什么基础

有帮助但不是必须的:了解 C 的基本概念——指针、栈和堆的区别、内存手动管理。如果你写过 C 或者 C++,Zig 里的很多设计会立刻感觉熟悉。

真正必须的:能读懂英文技术文档,能接受一门语言不像 Python/Java 那样"帮你做很多事"的设计风格。

Zig 的门槛不在语法,在思维模式的转换:你需要习惯"每一件事都是显式的"。内存从哪里分配?你说了算。错误怎么处理?你必须显式处理。这对习惯了 GC 语言的人来说需要一点适应时间,但不是很长。

一个有用的前置练习:在学 Zig 之前,花一两天写几个 C 程序——一个链表、一个简单的文件读写。不需要写得多好,只是为了建立"我要自己管内存"的意识。

二、学习路线:按这个顺序

第一步:zig.guide(一两天)

zig.guide 开始,不要从官方文档开始。官方文档(ziglang.org/documentation)非常详细,但它是参考手册,不是教程,线性读下去会失去方向感。

zig.guide 是目前最好的入门教程,覆盖了语言的主要特性,示例代码可以直接运行,更新频率也较高。用一两天过一遍,重点不是记住所有 API,而是建立对这门语言的整体感觉。

第二步:Ziglings(边做边学)

Ziglings 是一个练习项目——106 个有 bug 的小程序,你的任务是修复它们让测试通过,每道题都有注释提示。覆盖了 Zig 的核心概念,每道题 5-15 分钟能做完。

git clone https://github.com/ratfactor/ziglings
cd ziglings
zig build

做 Ziglings 的方式:不要跳过,按顺序做。碰到卡住的题,先自己想 10 分钟,再看提示,实在不行再看答案——但要确保你理解了答案,而不是复制粘贴过去。

第三步:官方标准库源码

Zig 学习里最被低估的资源是标准库源码本身。标准库用 Zig 写成,代码质量很高,里面有大量真实使用 comptime、分配器、错误处理的例子。

找到你本地的 Zig 安装目录,里面有一个 lib/std/ 文件夹。当你对某个标准库函数的用法不确定时,直接去看它的实现。这比看文档更有用,因为你能看到完整的上下文和边界情况处理。

第四步:做一个真实项目(最重要)

到这一步,你已经有了足够的基础知识,剩下的只能靠写代码来学。

三、三个最值得花时间的概念

Zig 有很多特性,但有三个是学习过程中最容易卡住、也是最值得投入时间的:

1. 分配器:彻底搞懂它

分配器(Allocator)是 Zig 里最陌生的概念。核心是:任何需要动态分配内存的操作,都需要你传入一个分配器。

const std = @import("std");

pub fn main() !void {
    // 通用分配器,用于一般场景
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();  // 程序结束时检测内存泄漏
    const allocator = gpa.allocator();

    // Arena 分配器:所有分配在 deinit 时一次性释放
    var arena = std.heap.ArenaAllocator.init(allocator);
    defer arena.deinit();
    const arena_alloc = arena.allocator();

    // 用 arena 分配器构建一个列表
    var list = std.ArrayList(u32).init(arena_alloc);
    try list.append(1);
    try list.append(2);
    // arena.deinit() 会释放 list 的所有内存,不需要手动 list.deinit()
}

学分配器的正确方式是:把四种主要分配器都用一遍——GeneralPurposeAllocator(通用)、ArenaAllocator(竞技场,适合请求/响应这类"批量分配批量释放"的场景)、FixedBufferAllocator(在固定缓冲区上分配,不做任何系统调用,适合嵌入式)、std.testing.allocator(测试专用,自动检测泄漏)。

理解了分配器,你就理解了 Zig 对内存的核心主张:分配的来源和分配的策略是可以独立控制的

2. comptime:先用,再理解

comptime 概念上不难(在编译期执行普通 Zig 代码),但"什么时候用、能用来做什么"需要一点练习才能建立直觉。

最快的学习路径是:先从泛型函数入手,看明白"类型也是值"这件事:

// T 是编译期参数,类型本身是一等公民
fn Stack(comptime T: type) type {
    return struct {
        items: []T,
        len: usize,

        const Self = @This();

        pub fn init(allocator: std.mem.Allocator) !Self {
            return Self{
                .items = try allocator.alloc(T, 16),
                .len = 0,
            };
        }

        pub fn push(self: *Self, item: T) void {
            self.items[self.len] = item;
            self.len += 1;
        }
    };
}

// 使用:用类型参数实例化
var int_stack = try Stack(i32).init(allocator);
var str_stack = try Stack([]const u8).init(allocator);

理解了"函数可以返回类型"这件事,comptime 的其他用法(条件编译、编译期计算、编译期反射)都是同一个机制的延伸。

3. 错误处理:不要用 catch unreachable 绕过去

Zig 的错误处理机制(错误联合类型 !Ttrycatch)设计清晰,但初学者容易用 catch unreachable 或者 catch {} 把错误静默掉,导致程序行为不可预测。

// 不好的写法(掩盖了错误)
const value = someFunction() catch unreachable;

// 好的写法:明确处理每种可能的错误
const value = someFunction() catch |err| switch (err) {
    error.OutOfMemory => return error.OutOfMemory,  // 向上传播
    error.InvalidInput => {
        std.debug.print("invalid input, using default\n", .{});
        break :blk default_value;
    },
};

// 或者简单向上传播
const value = try someFunction();

好的练习是:写一个程序,让它的所有错误路径都有明确的处理,不用任何 unreachable。这会强迫你真正思考"这里会出什么错、我要怎么处理",而不是让错误消失。

四、做什么项目练手

按难度递增,选一个做:

入门(一两天):命令行工具

写一个 wc 的简单实现——统计文件的行数、单词数、字节数。这个项目会让你用到:文件 I/O、字符串处理、标准输出、命令行参数解析。小但完整,Zig 标准库在这些方面支持很好。

./wc myfile.txt
# 输出: 42 lines, 156 words, 2048 bytes

进阶(三五天):HTTP 服务器

用 Zig 的内置 HTTP 服务器(std.http.Server)写一个能响应 GET 请求的简单 HTTP 服务器。这会用到分配器(处理请求需要动态分配缓冲区)、错误处理、并发的基础知识。

中级(一两周):解析器

写一个 JSON 解析器(或者更简单的 CSV 解析器)。解析器是练习 Zig 的好项目,因为它需要精确的内存控制、大量的错误处理、以及 comptime 在某些情况下能带来很好的设计。标准库里有一个 JSON 解析器,可以在写完之后对比看。

五、卡住了怎么办

第一选择:读标准库源码。如果你不知道某个 API 怎么用,找到它的实现,看看标准库里哪里调用了它,模仿那个用法。标准库的测试代码尤其有参考价值——lib/std/xxx_test.zig 里有大量使用示例。

第二选择:Zig Discord 和 Ziggit(论坛)。Zig 社区规模不大但质量高,提问通常能快速得到有帮助的回复。提问时粘上你的代码和编译器报错信息。

第三选择:搜索 GitHub 上的 Zig 项目。找几个用 Zig 写的开源项目(Bun 的 Zig 部分、TigerBeetle 等),看真实项目里怎么组织代码。代码量不用读完,找你不确定的那个具体模式怎么写就够了。

遇到编译器报错看不懂:Zig 的错误信息通常很精确,但对初学者来说格式不够友好。最有用的技巧是:把报错里的关键词(比如具体的类型名)粘进搜索引擎,加上 "zig" 关键词,通常能找到相似的问题讨论。

六、关于版本的注意事项

Zig 目前(2026 年)还是 pre-1.0,每个版本之间有破坏性变更。学习时用 nightly 版本或者最新的稳定版(当前是 0.14.x),不要用旧版本——网上很多教程是旧版本写的,有些代码在新版本里编译不了。

遇到网上的代码示例运行失败,先确认是不是版本问题。查一下 Zig 的 CHANGELOG 或者直接问社区"这段代码在最新版本里怎么写",通常很快能解决。

七、总结

  • 顺序:zig.guide → Ziglings → 标准库源码 → 真实项目
  • 三个核心概念:分配器(把四种都用一遍)、comptime(从泛型函数入手)、错误处理(不用 unreachable 绕过)
  • 练手项目:wc 命令行工具 → HTTP 服务器 → 解析器
  • 卡住时:标准库源码 → Zig Discord → GitHub 上的 Zig 项目
  • 版本:用最新版,遇到示例代码失败先检查版本兼容性

Zig 的学习曲线主要来自思维模式的切换,而不是语言的复杂性。一旦习惯了"所有东西都显式"这个设计原则,语言本身是相当一致且可预测的——这也是它值得学的原因之一。