跳转至

高效程序员的函数 — 块、defer 与 unreachable

这一章表面讲函数,实际交付三样东西: defer(把「稍后清理」钉在作用域出口, 无论怎么退出都执行)、unreachable(断言某条路不存在;Debug 档替你抓违约, Release 档把它当优化假设)、块即表达式(带标签的块 blk: 能返回值)。 函数本身反而简单——三行讲完定义,剩下的全是 Zig 式的纪律: 参数默认不可变、不许遮蔽。

1. 这一章讲什么

链条位置:第 4 章让代码会拐弯,这一章把重复的逻辑打包成可调用的单元, 并给第 6 章的错误处理铺路——defer 和 unreachable 都是错误处理的零件, 本章先各教一半。

2. 顶层全景

  块 { }        → 作用域的笼子;带标签 + break :标签 值 → 变成表达式
  fn 定义       → 有名、可复用的块;参数默认不可变;函数是一等公民(能当值传来传去)
  defer         → 「出这个作用域时执行我」,多个 defer 逆序执行
  unreachable   → 「到不了这里」;Debug/ReleaseSafe 档违约即 panic,
                  ReleaseFast/Small 档被当成真理优化

  图说:前两个是组织工具,后两个是「和编译器签的合同」——合同在 Debug 档有执行,
       在 Release 档变成假设。

主线例子:divide(10, 2) 与 defer 的输出顺序走查——两个例子分别承担 「函数契约」和「defer 栈」两条线。

3. 块:作用域笼子,还是能返回值的表达式

3.1 块关住变量

内层块里声明的变量,出块即销毁;外面想用就是编译错误。这在第 3 章见过, 本章把它升级为带标签的表达式1:

  const result = blk: {
      const a = 10;
      const b = 20;
      break :blk a + b;      // 跳出块,顺便把值 30 带出去
  };
  // result = 30;a 和 b 在这里已经不存在

blk: 给块起名,break :blk 表达式 把块的「值」定为那个表达式2。 原书的比方:在厨房里做饭,块藏起中间步骤(a 和 b),最后端出成品(result)3。 这个构造在第 4 章见过近亲(循环表达式),到第 6 章错误处理还会再见一次—— Zig 里「块/循环/分支都有值」是一个统一的表达式世界观。

两个边角(原书):空块 {} 等价于 void{},值和类型都是 void4; 遮蔽规则复述一遍——重叠作用域里同名重声明是错误 cannot redeclare 'pi', 完全不重叠的两个块里各叫 value 则没问题,因为生命周期不相交5。

4. 函数:定义三行,纪律三条

4.1 定义与调用

  fn add(a: i32, b: i32) i32 {
      return a + b;
  }

fn 名字(参数: 类型, ...) 返回类型 { 函数体 }。不返回值就写 void 或省略6。 返回 optional 也可以:fn divide(a: i32, b: i32) ?i32——b 为 0 时 return null, 否则返回商7;调用方按第 4 章的 if (result) |r| 解包处理。 这个 divide 就是「错误处理的雏形」:本章用 null 表达失败,第 6 章会换成专门的错误类型。

4.2 纪律一:参数默认不可变

函数参数是 const:在函数体里改参数直接编译错误(cannot assign to constant)。 想改,就造一个新变量:var result = n + 1; return result;8。

传参方式(原书口径):整数、浮点这类基本类型按值传——函数拿到的是副本, 改副本不影响外面;数组、结构体这类复杂类型,按值还是按引用由 Zig 自己挑, 挑的依据是哪个更高效,你不用管9。

判断(我们的,不是书里的): 「复杂类型由编译器挑」是一句轻松的省心事, 但它有个值得知道的后果:你不能从语法上推断一次调用会不会拷贝大对象, 想知道就得看生成的代码或文档。这和 Zig 其他地方的「一切显式」有点别扭—— 它在性能敏感处替你做了主。好在 Zig 的惯例是:真要保证不拷,就用指针参数(第 9 章), 把决定权拿回来。 如果错,会错在: 如果某个版本的 Zig 对大型结构体按值拷贝的策略和读者假设相反, 性能剖析会给出出乎意料的热点;但语义上(不改外面)两种传法是一致的,正确性不受影响。

4.3 纪律二:函数是一等公民

函数可以赋给变量、当参数传、当返回值。原书的例子:

  const MathOp = fn (a: i32, b: i32) i32;   // 函数类型:两个 i32 进、一个 i32 出

  fn applyOperation(op: MathOp, a: i32, b: i32) i32 {
      return op(a, b);
  }

  const sum = applyOperation(add, 5, 3);        // 8
  const difference = applyOperation(subtract, 5, 3);  // 2

fn (i32, i32) i32 本身是一个类型,可以和别的类型一样使用10。 这给了 Zig 函数式味道的基础设施,但注意:没有闭包(不能捕获外部变量的匿名函数)—— 想要「携带状态的回调」,得用手动传上下文指针的模式(第 9、12 章的常见手法)。 这是 Zig 刻意保持简单的又一处。

5. defer:钉在出口的清理

5.1 语义

defer 语句 = 「当前作用域无论以何种方式退出(正常走完、return 提前、错误抛出), 都先执行我」11。典型场景是资源释放:

  fn readFileContents(filename: []const u8) void {
      const file = openFile(filename);
      defer closeFile(file);        // 出函数时关文件,怎么出去都关

      const contents = readAll(file);
      // ...哪怕这里出错返回,closeFile 也保证执行
  }

原书强调的价值:防止泄漏、「即使出错或提前 return 也清理」12。 把「申请」和「释放」在源代码里写成紧挨的两行,是 Zig 代码的全局风格—— 你以后读任何 Zig 项目,都会看到 xxx(); defer yyy(); 这个固定对子。

5.2 主线例子:defer 的栈

  pub fn main() void {
      defer std.debug.print("First defer\n", .{});
      defer std.debug.print("Second defer\n", .{});
      std.debug.print("In main function\n", .{});
  }

输出(照原书)13:

  In main function
  Second defer
  First defer

多个 defer 逆序执行——像叠盘子,后放先拿14。机制上, defer 语句被压进一个栈,作用域退出时从栈顶弹:

  进入 main
    压栈 defer#1 "First defer"
    压栈 defer#2 "Second defer"
    打印 "In main function"
  退出 main:弹 defer#2 → 打印 "Second defer"
             弹 defer#1 → 打印 "First defer"

  图说:先声明的后执行。和资源依赖配对时用(先开的后关),顺序刚好天然正确。

6. unreachable:和编译器签的「不存在」合同

6.1 语义与两档行为

unreachable; 断言:执行到这一行就是出大事了。两档行为15:

构建档 到了 unreachable 会怎样
Debug / ReleaseSafe panic,报错信息「reached unreachable code」
ReleaseFast / ReleaseSmall 编译器假定这条路真的不存在,据此优化;真走到了,未定义行为

这正是第 1 章四档安全表的又一行兑现:安全检查分档购买。

6.2 什么时候用

典型场景:你确定某个分支到不了,但编译器不知道。比如 switch 的穷举检查 对宽类型(u8 有 256 个值)要求 else,而你的业务里只可能出现 1 和 2:

  fn getNumber(code: u8) u8 {
      return switch (code) {
          1 => 42,
          2 => 84,
          else => unreachable,     // 编译器要 else,你给断言
      };
  }

unreachable 的类型是 noreturn——「不返回」类型,和 break、continue、return 同族; noreturn 和任何类型兼容,所以它能出现在「期望一个 u8」的 else 分支里16。

6.3 它的黑暗面(必须记)

书里的警告原文照收:滥用 unreachable 会导致未定义行为—— ReleaseFast/ReleaseSmall 下,编译器把 unreachable 当真理, 走到那里的程序行为完全不可预期17。所以判据只有一条: 不是「我觉得到不了」,而是「到不了是被别处代码保证的」。 保证来源可以是:调用处已检查、类型约束、刚刚断言过。

6.4 assert 就是 unreachable 的包装

标准库的断言函数,实现只有两行(原书贴了源码)18:

  pub fn assert(ok: bool) void {
      if (!ok) unreachable;
  }

assert(2 + 2 == 5) 在 Debug 档 panic、帮你抓逻辑错误;在 Release 档, 整个检查被优化掉、零开销19。断言在 Zig 里是免费的(发布时)—— 这和第 7 章测试是互补关系:断言守不变量,测试守行为。

7. export:把函数亮给外部

export fn add(...) 让函数可以被 Zig 模块之外调用——被别的语言、被动态库链接20。 本章只立名字;C 互操作(第 12 章)会大量使用它。

8. 作者的判断与证据

可核的: 块表达式与 break :blk2;参数不可变与按值传基本类型89; 函数类型与一等公民10;defer 语义与逆序输出1314;unreachable 的两档行为 与 panic 信息15;noreturn 与任何类型兼容16;assert 的实现18;export 用途20。

立场: 「参数不可变让函数更纯更可预测」8、「不许遮蔽防混乱」(第 3、5 章重复两次)、 「defer 应该成为肌肉记忆」——都是 Zig 社区的主流价值,作者是它的传声筒,不是发明者。

9. 边界与局限

缺什么 说明
没有讲递归、栈帧、调用开销 函数章不讲函数调用的机器层面,对系统级语言是明显留白(第 9 章的栈/堆会补一部分)
泛型函数没出现 fn add(comptime T: type, ...) 这种是 Zig 泛型的日常形态,本章完全没提,要等到第 12 章 comptime
defer 的错误路径语义浅尝辄止 「出错也执行」一句话带过,但 try/错误返回和 defer 的交互(第 6 章才有 try)才是 defer 真正的主战场
函数指针 vs 函数类型没区分 const MathOp = fn(...) ... 声明的是函数指针类型;Zig 里协变的细节(比如运行时函数指针调用约定)没讲
章末 18 题里 Question 16 是最有价值的 给一段故意遮蔽的代码让改错21,比选择题更接近真实排错;可惜全章只有这一道

10. 可带走的

主线例子两行: defer First; defer Second; print In main → 输出 In main / Second / First(逆序);divide(10, 2) → ?i32 解包得 5, divide(10, 0) → null 走 else。

  1. 块是表达式:blk: { ... break :blk 值 } 让块有值,中间变量出块即销毁;
  2. 函数参数默认不可变:想改就造新变量;基本类型按值传,复杂类型的传法由编译器择优;
  3. 函数是一等公民:fn (i32, i32) i32 是类型;但没有闭包;
  4. defer 钉清理:申请和释放在源码里紧挨成对;作用域怎么退出都执行;
  5. 多个 defer 逆序执行(栈,后压先弹);
  6. unreachable 是合同不是注释:Debug 档违约 panic,Release 档违约是未定义行为; 判据是「被别处保证」,不是「我觉得」;
  7. noreturn 与所有类型兼容,所以 unreachable 能填任何「期望有值」的分支;
  8. assert = if (!ok) unreachable:Debug 抓错、Release 零开销;
  9. export fn 把函数亮给外部链接(第 12 章 C 互操作用的);
  10. 参数不可变 + 不许遮蔽,这两条和第 3 章的变量纪律合起来, 构成 Zig「数据默认只读」的完整面貌。

11. 原文地图

主题 原书章 原文位置
块与作用域 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:33(搜「limit the scope」)
块表达式 blk:/break :blk Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:57(搜「blk:」) · text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:67(搜「break :blk」)
空块 = void{} Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:119(搜「equivalent to void{}」)
遮蔽禁令与不重叠例外 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:93(搜「cannot redeclare 'pi'」) · text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:114(搜「declared in two different scopes」)
函数定义 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:138(搜「fn add(a: i32, b: i32)」)
参数不可变 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:146(搜「parameters are the inputs」)
基本类型按值传 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:152(搜「passed by value」)
复杂类型传法由编译器决定 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:166(搜「depending on what’s more efficient」)
返回值与 optional 返回 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:188(搜「return type after the parameter list」) · text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:207(搜「return null; // Cannot divide」)
函数是一等公民 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:244(搜「first-class citizens」) · text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:247(搜「const MathOp」)
unreachable 两档行为 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:293(搜「reached unreachable code」) · text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:294(搜「may optimize accordingly」)
noreturn 类型 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:326(搜「has the noreturn type」)
getNumber 例子 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:330(搜「fn getNumber」)
滥用 = 未定义行为 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:350(搜「misuse can lead to undefined behavior」)
assert 的实现 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:359(搜「pub fn assert」)
defer 语义 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:375(搜「when the current scope exits」)
defer 关文件例 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:399(搜「fn readFileContents」)
defer 逆序与输出 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:411(搜「reverse order」) · text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:419(搜「In main function」)
export 关键字 Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:424(搜「export keyword」)
章末 Lock it in Chapter 5: Functions for the Efficient Programmer text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:455(搜「Lock it in」) · text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:531(搜「Answers」)

  1. 出处:「Chapter 5: Functions for the Efficient Programmer」第 32–58 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:33,搜「limit the scope」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:52,搜「act as expressions」)。 ↩

  2. 出处:同章第 57–75 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:67,搜「break :blk」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:68,搜「evaluates to 30」)。 ↩↩

  3. 出处:同章第 77–80 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:78,搜「cooking something in a kitchen」)。 ↩

  4. 出处:同章第 117–128 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:119,搜「equivalent to void{}」)。 ↩

  5. 出处:同章第 81–115 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:93,搜「cannot redeclare 'pi'」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:114,搜「declared in two different scopes」)。 ↩

  6. 出处:同章第 187–193 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:188,搜「return type after the parameter list」)。 ↩

  7. 出处:同章第 201–213 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:207,搜「return null; // Cannot divide」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:212,搜「return null to indicate」)。 ↩

  8. 出处:同章第 145–163 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:146,搜「parameters are the inputs」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:157,搜「cannot assign to constant」)。 ↩↩↩

  9. 出处:同章第 152–153 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:152,搜「passed by value」)与第 165–167 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:166,搜「depending on what’s more efficient」)。 ↩↩

  10. 出处:同章第 243–264 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:244,搜「first-class citizens」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:247,搜「const MathOp」)。 ↩↩

  11. 出处:同章第 373–396 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:375,搜「when the current scope exits」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:375,搜「regardless of how it exits」)。 ↩

  12. 出处:同章第 398–409 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:409,搜「even if an error occurs」)。 ↩

  13. 出处:同章第 411–423 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:419,搜「In main function」)。 ↩↩

  14. 出处:同章第 411 与 423 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:411,搜「reverse order」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:423,搜「stacking dishes」)。 ↩↩

  15. 出处:同章第 285–308 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:293,搜「reached unreachable code」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:294,搜「may optimize accordingly」)。 ↩↩

  16. 出处:同章第 326–328 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:326,搜「has the noreturn type」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:328,搜「compatible with all types」)。 ↩↩

  17. 出处:同章第 350–354 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:350,搜「misuse can lead to undefined behavior」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:353,搜「behavior of your program is undefined」)。 ↩

  18. 出处:同章第 356–361 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:359,搜「pub fn assert」)。源码链接(查阅于 2026-10-19):https://github.com/ziglang/zig/blob/ea527f7a850f0200681630d8f36131eca31ef48b/lib/std/debug.zig#L404 ↩↩

  19. 出处:同章第 362–369 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:365,搜「cause a panic in debug modes」)。 ↩

  20. 出处:同章第 424–433 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:424,搜「export keyword」;text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:431,搜「available for external linkage」)。 ↩↩

  21. 出处:同章第 513–520 与 570–578 段(text/11-ch05-chapter-5-functions-for-the-efficient-programmer.txt:571,搜「use a different variable name」)。 ↩