跳转至

写 cat 第一版 — 分配器与 defer、错误集与 try,和 writer 接口

这一章讲什么: 第 3 章开始写真的东西:cat——那个把文件内容 原样打到终端的小命令。3.1 节的第一版 cat 只能读一个固定文件, 但作者绕着它把 Zig 的四大机制全部过一遍: 分配器(堆内存从哪来、谁负责还)、defer(资源什么时候放)、 错误处理(错误集、错误联合、try/catch)、writer 接口 (标准库怎么用一个模式统一所有「能写的东西」)。 读完你会看到:一个能跑但漏内存、一崩到底的 cat 长什么样—— 以及 Zig 怎么当场把它抓住。

1. 这一章讲什么

cat 的核心行为一句话:收下命令行传来的一组文件名,按顺序把内容写到 stdout1。但作者不直接写「正确版本」,而是照他真实的开发流程来: 先原型——用最短路径弄出能跑的东西;再一版版改进—— 每版发现问题、逐个修复,直到值得依赖2。

这一节的原型只实现一个行为:从文件读数据。 作者预判「读文件」会占这个程序复杂度的绝大部分,所以第一刀切在这里3。

2. 顶层全景:第一版 cat 的四个新器官

  输入:meow.txt(内容 "meow")
      │
      ├─ ① std.fs:跨平台的文件系统模块(cwd() 拿到当前目录这个 Dir)
      ├─ ② 分配器:文件大小未知 → 不能站栈上 → 向堆要 → 调用方递分配器
      ├─ ③ defer:出块前一定执行 → 还内存、关东西的统一挂点
      └─ ④ try:每一步都可能失败 → 失败就返回错误,由调用方决定怎么办

  图说:输出一行 "meow",外加一条泄漏报告——这是设计好的事故。

3. 核心原理

3.1 文件与句柄:std.fs 的抽象层

Zig 的文件系统操作住在 std.fs 模块。也可以用 std.os 直接碰 操作系统 API,但 std.fs 是跨平台的,已经备好了很多顺手的功能4。

三个角色:std.fs.File(与文件交互)、std.fs.Dir(与目录交互)。 为了正确处理相对路径,开文件要通过 Dir 来开; std.fs.cwd() 返回当前工作目录的句柄5。

句柄(handle)是什么:某种隐藏数据结构——比如文件系统——的标识符, 你可以把它想成「一个你摸不到的数组的下标」,有时候它的实现真就是一个 下标6。顺带,Zig 把 stdin/stdout/stderr 也当文件看7—— 所以打印到终端和写入文件,后面会是同一套接口。

Dir 上有个现成函数 readFileAlloc:替你读整个文件—— 但它要一个分配器。这就是下一节的入口。

3.2 分配器与 defer:泄漏当场被抓的现场教学

为什么不能照旧站栈上。 到目前为止的代码都只用栈内存:函数被调用时 自动分给它的那部分内存,编译器在程序跑之前就能算清要多少。 但栈只有几 MB——大数组一压就「栈溢出」,直接 segfault; 更糟的是溢出后可能不崩,带着烂数据继续跑8。

堆与系统调用。 大小未知的数据要「堆内存」:用系统调用 (system call,操作系统提供的功能入口:文件操作、要内存、聊网络, 最后都汇到这里9)向操作系统额外申请的内存。 分配器(allocator)就是「用统一接口管内存的数据结构」: 问它要一块、用、用完还(free 就是告诉它「这块可以拿去别处用了」)10。

范式翻转:别的语言藏着,Zig 摆在参数里。 在 C 和大多数语言里,分配不需要你显式给个分配器——更准确地说, 语言运行时里藏了一个全局分配器,悄悄服务所有堆操作。 你想换它的工作方式?没门。用第三方库?那它的内存决策你全盘接受。 「Zig 的分配器范式把这套隐式行为变成了显式:它让你控制代码里 每一点内存的分配方式」11。后果就是你在第 02 篇见过的: readFileAlloc(ally, filepath, max_bytes)——分配器是第一个参数。

DebugAllocator:默认起点,以及它的把戏。 书里默认从 DebugAllocator 起步:按大多数程序适用的方式管堆内存, 外加「一些花哨的内部动作」帮你在调试内存问题时出力12。 把戏是什么?先看运行——第一版 cat 跑起来:

  $ echo "meow" > meow.txt
  $ zig run cat_allocating.zig
  meow
  error(gpa): memory address 0x7ff5a1da0000 leaked:
  ...(栈回溯,指向 readFileAlloc 里的分配点)...

  图说:程序说 "meow" 了——然后告状:有一块内存你没还。
  地址是原书示例机的,你的机器上会不一样。

泄漏是怎么被抓的。 DebugAllocator 在调试优化模式下 为每一次分配记录一份栈回溯;程序退出时它盘点: 有谁没还,就把「哪行代码要的」打出来13。 栈回溯指认的正是 readFileAlloc 内部那次分配—— 文件内容读进来了,但没人告诉分配器「我用完了」。 修法一行:defer ally.free(text);——defer 的意思是: 从这行之后,任何一条离开这个代码块的路径,都会先执行 defer 挂的语句14。 正常走完要 free,中途出错返回也要 free——这正是 defer 的价值: 释放逻辑写一次,所有出口全覆盖。 「用 defer 释放资源」是 Zig 里极其常见的模式15。

预告:分配器家族。 原书给有经验的系统程序员留了个预告框: ArenaAllocator(作者的挚爱——大量分配、一次全 free,又快又省心)、 FixedBufferAllocator(从一块固定缓冲里抠)、 StackFallbackAllocator(先站栈,站不下再退堆)、 C allocator(用 C 标准库 malloc 那套,和 C 代码混用时保持同进退)16。 它们全都是同一个接口的不同实现——这正是「分配器范式」的复利: 换实现不换接口。

这一章主线例子(读 meow.txt 全程)到这里的账本:

  gpa.init            DebugAllocator 上岗
  gpa.allocator()     拿到接口(ally)——此后只跟接口打交道
  readFileAlloc       堆上分配一块,装进 "meow"(4 字节 + 元数据)
  打印 meow           内容到了,但分配的那块没人认领
  gpa.deinit()        盘点:发现 1 处泄漏,报地址 + 栈回溯
  ── 修复:defer ally.free(text) ──
  再跑:meow,无报错 —— 账平了

  图说:每个状态都是真实可跑的;泄漏报告是 DebugAllocator 的免费馈赠。

3.3 错误集、错误联合,和 try/catch 三件套

Zig 的 try 和别家语言那个配 catch/except 块的 try 完全不是一个东西—— 它操作的对象是错误联合(error union);而理解错误联合,得先理解错误集 (error set)17。

错误集:给「会怎么失败」起名字的集合。 Zig 的错误本质上就是错误码, 配了些语言特性让它们互相配合;很像很多语言里的枚举(enum)。 错误集就是「一组可能错误」的类型18:

const InvalidLengthError = error {
    TooLong,
    TooShort,
};

const too_long = InvalidLengthError.TooLong;   // 错误是值,能装进变量
const also_long = error.TooLong;               // error.X 单飞:自动造只含 X 的集合

错误也是值:能赋给变量、能传、能比较19。 窄集能强转成宽集——只要小集合整个被大集合包含, error{TooLong} 就是合法的 InvalidLengthError20。 另外还有个万能集合 anyerror(程序里全部错误的并集), 写通用代码时有用;但书里的建议是:没有过硬理由,别用它,写具体错误集21。

错误联合:要么错误,要么值。 函数签名里的 ! 造出错误联合: FibonacciError!i32 读作「要么是一个 FibonacciError,要么是一个 i32」; 那个 i32 叫 payload(载荷)22。函数体里返回错误和返回值一样自然: return error.OverFibonacciLimit;

const FibonacciError = error { OverFibonacciLimit };
const fibonacci_limit = 30;

fn fibonacci(n: usize) FibonacciError!i32 {
    if (n == 0) { return 0; }
    else if (n == 1) { return 1; }
    else if (n > fibonacci_limit) {
        return error.OverFibonacciLimit;   // 错误按普通值返回
    }
    return try fibonacci(n - 1) + try fibonacci(n - 2);
}

主线例子在错误上走一步:让 fibonacci 自己报「超界」。

  输入 n = 31(上限是 30)
  31 > 30 → 返回 error.OverFibonacciLimit(一个值,不是异常)
  main 里接它的两种方式:
    方式 A  catch -1:出错就用 -1 顶上
    方式 B  catch |e| {…}:把错误捉进变量 e,爱怎么处理怎么处理
    方式 C  try:出错就把错误原样传给「我」的调用方
  图说:n、上限 30、错误名都是程序里的真实字面值。

catch:最通用的接法。 const value = fibonacci(n) catch -1; 出错时执行 catch 右侧的表达式——写日志(把发生的事记进文件)、提前返回、 给默认值,什么都行。 但右侧的类型必须和 payload 相容;不想返回值,就用 return 之类 「不产生值的控制流」逃出去(这种「无值」的类型叫 noreturn, 与任何类型相容)23。

捕获组(capture group):把错误捉成变量。 管道符绑名字: fibonacci(n) catch |e| { return e; }——出错时错误值赋给 e24。

try:上面那个模式的缩写。 「捕获到就原样上抛」频繁到值得一个关键字: const value = try fibonacci(n); 等价于上面整个 catch 块25。 但要记住它的语义:try 等于宣布「调用方负责处理可能的错误」; 一路 try 到 main,就是放任程序崩—— Zig 的 start 代码会打出错误的名字和栈回溯26。 错误(error)这个东西,「是让代码显式」的关键工具之一; 这节只看了它的一角,全书会反复回来27。

3.4 Writer 与接口模式:一个 struct 通吃所有「能写的」

回到 listing 3.1 的最后一块:writer。前面见过无数次的那四行 (拿 stdout 文件 → 声明缓冲 → 造 writer → 取 interface), 原书在这里正式拆解。要点是模式,不是 API:

Writer 不止一种,正如分配器不止一种。 writer 可以写文件、 写网络连接、写内存里的缓冲区,或任何你能实现的输出—— 「泛型接口 + 底层具体实现」,和分配器一个套路28。 原书明说:Writer 和分配器内部工作原理相似29。

相似到什么程度?原书给了核心结构(listing 3.6):

const WriterInterface = struct {
    context: *const anyopaque,      // 指向「真正的 writer」的指针(擦掉类型)
    vtable: *const VTable,          // 一张函数表

    const VTable = struct {
        write_bytes: *const fn(context: *const anyopaque, bytes: []const u8),
    };

    fn writeAll(self: @This(), bytes: []const u8) void {
        self.vtable.write_bytes(self.context, bytes);
    };
};

接口模式就两层:一个擦掉了类型的上下文指针(anyopaque 告诉编译器 「我有理由不说明这指针的类型」30),加一张虚表(vtable)—— 里面存着「真正的实现」的函数指针。writeAll 自己不写文件: 它把调用转给 vtable 里的 write_bytes,由「真正的 writer」完成。 文件 writer 填一张表,网络 writer 填另一张表,你的自定义 writer 填你自己的表——上层代码一个 writeAll 通吃。

这个模式你其实已经见了两遍:分配器(ask for memory 的那张表) 和 writer。记住它——第 08 篇的 reader 还是它, Zig 标准库里一大半的「可替换能力」都是这个形状。

4. 作者的判断与证据

判断 是书里给的证据,还是作者的推测
读文件构成 cat 复杂度的绝大部分 作者的工程预判(合理,后文验证:确实迭代都围绕 IO)
DebugAllocator 适合当默认起点 作者建议;「调试模式记录每次分配的栈回溯」是可验证的机制
泄漏 free 的模式「极其常见」 经验陈述
别用 anyerror,「没有过硬理由写具体错误集」 社区惯例 + 作者建议;理由(失去编译器检查)没展开
Writer/分配器同构(接口模式) 机制事实,listing 3.6 可核

5. 边界与局限

  • readFileAlloc 的隐形上限:第一版代码里 max_bytes = 16 * 1024 * 1024 (16 MiB)——大于 16 MiB 的文件直接报错。这个上限是「最小原型」欠的账, 第 08 篇会连本带利还掉(流式化)。作者故意留着它当教学道具。
  • 泄漏检查只在 Debug 档:ReleaseFast 下 DebugAllocator 的账本和 检查都消失——「调试期抓、上线期快」这个交换又出现一次。
  • vtable 模式的教学有门槛:原书自己也插了条 TIP—— 「接口是个相当进阶的话题;指针还没站稳的话,先做笔记回头再看」31。
  • 错误处理漏了 errdefer:defer 的亲戚(出错时才执行), 在清理「半初始化」状态时才是正解,本节未提——已放出章节都没讲, 属于读者要自己补的坑。
  • _ = gpa.deinit(); 的丢弃写法:deinit 返回值(是否发现泄漏)被显式丢弃, 为什么 main 里要丢弃而不是处理,书里没有解释。

6. 可带走的

  1. 写实用程序的姿势:原型优先——最短路径先能跑,再逐轮迭代;
  2. 栈几 MB、大小未知走堆;堆通过分配器管,分配器由调用方传入;
  3. std.fs:跨平台文件操作;cwd() 拿目录句柄;相对路径经 Dir 开;
  4. defer = 「出块必执行」,释放资源写一次、覆盖所有出口;
  5. DebugAllocator 的免费馈赠:泄漏时给出分配的地址和栈回溯;
  6. 错误集 = 命名的失败集合;错误是值,窄集自动进宽集;
  7. !T 错误联合 = 「错误或 T」;payload 是 T 那半;
  8. 三件套:catch(通用接法)、catch |e|(捉成变量)、try(原样上抛);
  9. try 到 main = 放任崩溃;崩溃时 start 代码打印错误名 + 栈回溯;
  10. 接口模式 = anyopaque 上下文 + vtable 函数表; 分配器、writer、reader 全是它——一个模式记三次,赚两次。

7. 原文地图

主题 原书章 原文位置
cat 的行为与迭代工作流 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:2(搜「core functionality consists」) · text/16-ch03-01-3-1-meow-says-cat.txt:8(搜「iterative workflow」)
读文件是复杂度主体 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:14(搜「reading data from files」)
std.fs 与 cwd 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:19(搜「std.fs」) · text/16-ch03-01-3-1-meow-says-cat.txt:25(搜「current working directory」)
句柄定义 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:27(搜「HANDLE」)
栈内存与溢出 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:81(搜「stack memory」) · text/16-ch03-01-3-1-meow-says-cat.txt:84(搜「few megabytes」)
系统调用定义 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:89(搜「SYSTEM CALL」)
堆、分配器定义、free 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:96(搜「heap memory」) · text/16-ch03-01-3-1-meow-says-cat.txt:98(搜「common interface」)
隐式分配器 → 显式 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:113(搜「implicit behavior explicit」)
DebugAllocator 默认起点 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:118(搜「DebugAllocator, which manages」)
defer 的定义 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:121(搜「defer statement」)
分配器家族预告 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:126(搜「A TEASER FOR EXPERIENCED」)
泄漏报告与修复 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:150(搜「leaked」) · text/16-ch03-01-3-1-meow-says-cat.txt:164(搜「memory leak」) · text/16-ch03-01-3-1-meow-says-cat.txt:166(搜「stack trace for every allocation」)
try 不是别家的 try 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:192(搜「not like the try keyword」)
错误集定义 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:196(搜「effectively just error codes」) · text/16-ch03-01-3-1-meow-says-cat.txt:208(搜「Errors are just values」)
窄集进宽集;anyerror 建议 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:220(搜「narrower error set」) · text/16-ch03-01-3-1-meow-says-cat.txt:224(搜「anyerror」)
错误联合与 payload 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:253(搜「Error unions are defined」)
catch 最通用;类型须相容 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:263(搜「most general construct is catch」) · text/16-ch03-01-3-1-meow-says-cat.txt:288(搜「compatible with the payload」)
捕获组 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:295(搜「capture group」)
try = 简化上抛;调用方负责 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:316(搜「separate keyword, try」) · text/16-ch03-01-3-1-meow-says-cat.txt:321(搜「caller of a function is responsible」)
writer 是泛型接口;与分配器同构 3.1 Meow, says cat text/16-ch03-01-3-1-meow-says-cat.txt:362(搜「different kinds of Writers」) · text/16-ch03-01-3-1-meow-says-cat.txt:366(搜「work in a similar way」)
接口结构:anyopaque 与 vtable 3.2 Giving our cat more toys text/17-ch03-02-3-2-giving-our-cat-more-toys.txt:1(搜「What is an interface」) · text/17-ch03-02-3-2-giving-our-cat-more-toys.txt:23(搜「anyopaque keyword」)

  1. 出处:「3.1 Meow, says cat」第 1–5 段(图 3.1 的说明,text/16-ch03-01-3-1-meow-says-cat.txt:2,搜「core functionality consists」)。原文:「cat's core functionality consists of reading in a set of filenames passed as command line arguments and writing their contents to stdout in the same order.」 ↩

  2. 出处:「3.1 Meow, says cat」第 7–12 段(text/16-ch03-01-3-1-meow-says-cat.txt:8,搜「iterative workflow」)。原文:「we'll implement things in the same iterative workflow I use to write code. First, we'll aim for a prototype. Then we'll identify the critical behavior… Then we'll iterate.」 ↩

  3. 出处:「3.1 Meow, says cat」第 14–16 段(3.1.1,text/16-ch03-01-3-1-meow-says-cat.txt:14,搜「reading data from files」)。原文:「The most important low-level behavior in cat is reading data from files. We can reasonably assume that this will constitute most of the complexity of the program.」 ↩

  4. 出处:「3.1 Meow, says cat」第 19–21 段(text/16-ch03-01-3-1-meow-says-cat.txt:19,搜「std.fs」;:20,搜「cross-platform」)。原文:「Zig's file system operations live in the module std.fs.」「std.fs is cross-platform and implements a lot of handy functionality for us already.」 ↩

  5. 出处:「3.1 Meow, says cat」第 22–26 段(text/16-ch03-01-3-1-meow-says-cat.txt:22,搜「std.fs.File data structure」;:25,搜「current working directory」)。原文:「We use the std.fs.File data structure to interact with files, and std.fs.Dir to interact with directories.」「We'll use the std.fs.cwd() function here to get a handle to the current working directory of our terminal.」 ↩

  6. 出处:「3.1 Meow, says cat」第 27–31 段(HANDLE 栏,text/16-ch03-01-3-1-meow-says-cat.txt:27,搜「HANDLE」)。原文:「A handle is a kind of identifier to a data structure hidden from view, such as a filesystem. You can think of it like an index into an array you can't access. Sometimes this is exactly how it's implemented.」 ↩

  7. 出处:「3.1 Meow, says cat」第 33–35 段(TIP)。原文:「Zig thinks of stdin, stdout, and stderr as files!」 ↩

  8. 出处:「3.1 Meow, says cat」第 80–87 段(3.1.2,text/16-ch03-01-3-1-meow-says-cat.txt:81,搜「stack memory」;:84,搜「few megabytes」)。原文:「So far, we've only seen code that uses stack memory — that is, memory which is automatically assigned to a function when it's called.」「Stack memory is limited to a few megabytes. If we use too much… our program will 'overflow the stack' and trigger a segfault. Even worse, a stack overflow might just continue running with corrupted data instead of crashing.」 ↩

  9. 出处:「3.1 Meow, says cat」第 89–93 段(SYSTEM CALL 栏,text/16-ch03-01-3-1-meow-says-cat.txt:89,搜「SYSTEM CALL」)。原文:「Functions provided by the operating system to perform tasks facilitated by the operating system. Common examples include filesystem operations, requesting memory, talking to devices, and interacting with networks like the internet.」 ↩

  10. 出处:「3.1 Meow, says cat」第 95–107 段(text/16-ch03-01-3-1-meow-says-cat.txt:96,搜「heap memory」;:98,搜「common interface」)。原文:「Allocators are just data structures that manage memory with a common interface. You can allocate a chunk of memory using an allocator, and use it for any purpose you wish. Once you're done with it, you typically need to free the memory.」 ↩

  11. 出处:「3.1 Meow, says cat」第 109–115 段(text/16-ch03-01-3-1-meow-says-cat.txt:113,搜「implicit behavior explicit」)。原文:「In C and most languages, allocation typically does not require an allocator. Well, to be more specific, the language runtime contains an allocator that is used for all heap memory operations.」「Zig's allocator paradigm makes this implicit behavior explicit. In doing so, it gives you control over every bit of memory allocated in your code.」 ↩

  12. 出处:「3.1 Meow, says cat」第 117–120 段(text/16-ch03-01-3-1-meow-says-cat.txt:118,搜「DebugAllocator, which manages」)。原文:「By default, it's a good idea to start with DebugAllocator, which manages heap memory in a manner suitable for most programs and some fancy stuff behind the scenes to help us out when we need to debug memory issues.」 ↩

  13. 出处:「3.1 Meow, says cat」第 162–169 段(text/16-ch03-01-3-1-meow-says-cat.txt:166,搜「stack trace for every allocation」)。原文:「In the debug optimization mode… DebugAllocator will store a stack trace for every allocation to help us figure out where our leak occurred.」「when you allocate memory from an allocator, you must add a corresponding call to free the memory you allocated. Otherwise, in long-running programs, the operating system will slowly run out of memory over time.」 ↩

  14. 出处:「3.1 Meow, says cat」第 121–124 段(text/16-ch03-01-3-1-meow-says-cat.txt:121,搜「defer statement」;:122,搜「exits from the block」)。原文:「Once you see a defer statement in a block of code, any location in code following the defer statement that exits from the block will run the code within the statement.」 ↩

  15. 出处:「3.1 Meow, says cat」第 176–184 段(text/16-ch03-01-3-1-meow-says-cat.txt:183,搜「extremely common pattern」)。原文:「Using defer to release resources is an extremely common pattern in Zig.」 ↩

  16. 出处:「3.1 Meow, says cat」第 126–138 段(A TEASER FOR EXPERIENCED SYSTEMS PROGRAMMERRS 栏,text/16-ch03-01-3-1-meow-says-cat.txt:126,搜「A TEASER FOR EXPERIENCED」;:129,搜「My favorite allocator」)。四种分配器逐条照原书预告框。 ↩

  17. 出处:「3.1 Meow, says cat」第 190–195 段(3.1.3,text/16-ch03-01-3-1-meow-says-cat.txt:192,搜「not like the try keyword」;:194,搜「error union values」)。原文:「Zig's try is not like the try keyword in many other languages, commonly paired with a catch or except block. Instead, Zig's try operates on error union values.」 ↩

  18. 出处:「3.1 Meow, says cat」第 196–199 段(text/16-ch03-01-3-1-meow-says-cat.txt:196,搜「effectively just error codes」)。原文:「Zig's errors are effectively just error codes with some special language features to make them play nice with each other. They are similar to an enumeration (enum) in many programming languages. We define and use errors through error sets, a type that encapsulates a set of possible errors.」 ↩

  19. 出处:「3.1 Meow, says cat」第 208–210 段(text/16-ch03-01-3-1-meow-says-cat.txt:208,搜「Errors are just values」)。原文:「Errors are just values, so you can hold them in variables like any other value.」 ↩

  20. 出处:「3.1 Meow, says cat」第 220–223 段(text/16-ch03-01-3-1-meow-says-cat.txt:220,搜「narrower error set」)。原文:「Values of a narrower error set can coerce to wider error sets, as long as their set is fully contained by the wider set!」 ↩

  21. 出处:「3.1 Meow, says cat」第 224–226 段(TIP,text/16-ch03-01-3-1-meow-says-cat.txt:224,搜「anyerror」)。原文:「In general, you should stick to specifying error sets without a very good reason to avoid it.」 ↩

  22. 出处:「3.1 Meow, says cat」第 253–257 段(text/16-ch03-01-3-1-meow-says-cat.txt:253,搜「Error unions are defined」)。原文:「Error unions are defined using the ! operator. They are values that either be an error or an inner value, known as a payload.」 ↩

  23. 出处:「3.1 Meow, says cat」第 263–293 段(listing 3.3 与 catch 讲解,text/16-ch03-01-3-1-meow-says-cat.txt:263,搜「most general construct is catch」;:288,搜「compatible with the payload」)。原文:「Catch… lets you gracefully handle errors when they occur.」「the error condition of catch must be compatible with the payload!」 ↩

  24. 出处:「3.1 Meow, says cat」第 295–307 段(listing 3.4,text/16-ch03-01-3-1-meow-says-cat.txt:295,搜「capture group」)。原文:「using a capture group to assign a variable name to the error value. Capture groups are a type of syntax that binds a name to a captured value, enclosed in pipes (|).」 ↩

  25. 出处:「3.1 Meow, says cat」第 315–319 段(text/16-ch03-01-3-1-meow-says-cat.txt:316,搜「separate keyword, try」)。原文:「This exact pattern of returning an error up the stack is so common in Zig that we have a separate keyword, try, to simplify using it.」 ↩

  26. 出处:「3.1 Meow, says cat」第 321–324 段(text/16-ch03-01-3-1-meow-says-cat.txt:321,搜「caller of a function is responsible」)。原文:「the try syntax implies that the caller of a function is responsible for any potential errors. If an error is returned from main, we're effectively allowing the program to crash if any of these fail.」 ↩

  27. 出处:「3.1 Meow, says cat」第 325–327 段(text/16-ch03-01-3-1-meow-says-cat.txt:324,搜「critical tools」)。原文:「The error is a unique element of Zig. It is one of the critical tools Zig uses to make code explicit.」 ↩

  28. 出处:「3.1 Meow, says cat」第 329–335 段(3.1.4 与 TIP,text/16-ch03-01-3-1-meow-says-cat.txt:333,搜「pretty advanced Zig topic」)与第 362–365 段(text/16-ch03-01-3-1-meow-says-cat.txt:362,搜「different kinds of Writers」)。原文:「Just as there are different kinds of Allocators, there are different kinds of Writers. A writer can write to a file, over a network connection, into a buffer, or to any other output you can implement. They are a generic interface with an underlying implementation, just like allocators.」 ↩

  29. 出处:「3.1 Meow, says cat」第 366–369 段(text/16-ch03-01-3-1-meow-says-cat.txt:366,搜「work in a similar way」)。原文:「Internally, Writers and Allocators work in a similar way. The core functionality of writers boils down to something like listing 3.6.」 ↩

  30. 出处:「3.2 Giving our cat more toys」第 23–25 段(text/17-ch03-02-3-2-giving-our-cat-more-toys.txt:23,搜「anyopaque keyword」)。原文:「The anyopaque keyword can be helpful in generic code. It tells the compiler that we have a good reason to avoid specifying the type of data this pointer references.」 ↩

  31. 出处:「3.1 Meow, says cat」第 333–335 段(TIP,text/16-ch03-01-3-1-meow-says-cat.txt:333,搜「pretty advanced Zig topic」)。原文:「Interfaces are a pretty advanced Zig topic. If you're not entirely on your feet with pointers yet, this might be confusing. It's okay to make a note and return to this!」 ↩