编程语言研究书库

函数与模块——全书最长一章,虚构语法最密集的一章

函数与模块——全书最长一章,虚构语法最密集的一章

这一章讲两件事: 原书第五章讲函数(定义、参数、返回值、重载、变参、递归、 匿名函数),第六章讲模块(建模块、导入导出、包管理、路径别名)—— 第六章的正文在转码时并进了第五章的文件末尾,内容没丢1。 两章合计约 2.8 万字符,是全书最大的单块。它的虚构密度也是最高的: 我们逐节对账,你会看到一本教材如何系统性地把 C++/Go/Rust/JavaScript 的函数和模块机制,一样一样安到 Zig 名下。

1. 这一章讲什么

  书的结构                     一句话内容              我们的对账结果
  ──────────────────────────────────────────────────────────────
  5.1 定义函数                 fn 关键字、参数、返回值   骨架真
  5.2 参数与返回值             多参数、默认参数、多返回值  默认参数假,其余半真
  5.3 函数重载                 同名不同参、按返回类型重载  整节假(Zig 无重载)
  5.4 变长参数列表             ...i32 语法、slice 内建函数 整节假
  5.5 递归                     阶乘、斐波那契、尾递归    概念真,两个细节假
  5.6 匿名函数(闭包)         fn 字面量、捕获外部变量    词半真,闭包假
  6.1–6.2 模块与导入导出        文件即模块、pub、通配导入  前半真,通配/别名假
  6.3 包管理                   zig build -deps、zig.mod 基本全假
  6.4 模块路径与别名            相对/绝对路径、as 别名     路径半真,as 假

  图说:十节里五节全假或基本全假。这张表就是这一章的账目。

2. 顶层全景:一个 divide 函数的两世

拿书 5.1 节的主线例子走一遍。书想教的是「一个函数如何同时返回商和「除没除成」」:

// 书里写的:
fn divide(x: i32, y: i32) (i32, bool) {
    if y == 0 {
        return (0, false);
    }
    return (x / y, true);
}
  走查(按书的说法)              走查(真实 Zig 编译器视角)
  ──────────────────────────────────────────────────────────
  调用 divide(7, 2)            编译期:类型 `(i32, bool)` 不是合法返回类型写法,
  → y=2 不为 0                  函数签名这行直接报错,走不到下一步
  → 返回 (3, true)             (真实 Zig 没有裸元组类型这个语法)
  调用 divide(7, 0)            真实 Zig 里这件事的标准写法是「错误联合」:
  → y=0                        fn divide(x: i32, y: i32) !i32
  → 返回 (0, false)              ——返回类型带个 !,表示「可能返回错误」;
                                 除零处写 return error.DivideByZero;
                                 调用处用 catch 或 try 接住。

  图说:同一段意图,书给的是「多返回值+布尔标记」,真实 Zig 给的是「错误是值」。
  两种写法差的不只是语法,是设计哲学(见下)。

这个对比是整章最重要的一步,值得讲透。书的做法(返回一个值加一个布尔标记) 是 C 时代的通行解法,毛病是调用者可以随手丢掉那个布尔——标记没人查, 除零的 bug 照样炸。真实 Zig 的做法是让「可能失败」写进类型(!i32), 失败路径和处理路径在类型上就是分开的,想不处理都不行(编译器会逼你接住)。 官方文档的说法是「错误是值,且不可被忽略」2。书里教的那种布尔标记, 恰恰是 Zig 设计里要淘汰的东西——同第 04 章 switch 一节一模一样的 pattern。

3. 核心原理之一:函数的基本形态——真的那一半

fn greet(name: []const u8) void { ... } 这个基本形态是真实的: fn 开头、参数名冒号类型、返回值类型写在参数列表后、void 表示不返回值3。 []const u8 这个字符串类型也是真的(第 03 章已核)。

但有一处要修正,而且它暴露全书最大的问题: 5.1 节说参数可以有默认值 (greeting: []const u8 = "Hello"),5.2 节又教一遍4。 真实 Zig 没有默认参数。 想达到同样效果,真实做法是定义两个函数, 或者用可选参数类型(?[]const u8)在函数体里用 orelse 补默认值。 这不是 Zig 漏了功能——官方对函数设计的立场是「一种事情一种写法」, 默认参数会引出重载,重载会引出歧义,Zig 把这条歧义链从源头砍掉了。 下一节你会看到,书紧接着就教了重载——书教的两样东西, 恰好是 Zig 故意不做的两样东西。

4. 核心原理之二:函数重载——整节是 C++ 的

书 5.3 节完整教了 C++ 式的函数重载:同名函数按参数类型区分 (fn add(x: i32, y: i32) 与 fn add(x: f64, y: f64) 并存), 按参数个数区分,甚至按返回类型区分5。

  书的三个重载判据             真实 Zig 的反应
  ──────────────────────────────────────────────────
  按参数类型区分                编译错误:重复定义(duplicate)
  按参数个数区分                编译错误:重复定义
  按返回类型区分                编译错误:重复定义——而且书自己也
                               承认「不推荐」,教完再劝退

  图说:三个判据,真实 Zig 只有一句台词。

书里还有一句很诚实的真话被埋在这一节:它说按返回类型重载「不推荐, 会让代码更难读难维护」5。把这句放大,就是 Zig 对整个重载机制的态度—— 所以 Zig 干脆没有它。 真实 Zig 里想「同名不同用」,惯用做法是把类型变成 编译期参数(fn add(comptime T: type, x: T, y: T) T,一个函数吃所有类型)—— 这就是第 11 章要讲的真泛型,它天然替代了重载。

顺带解释一个术语:重载(overloading)指允许同名函数按参数列表不同 而共存的机制;泛型(generics)指写一个对多种类型都成立的函数或类型。 书把前者当 Zig 的特性教、把后者写成 C++ 模板的样子(第 11 章拆), 而真实 Zig 的路线是:用泛型(而且是完全不同的实现思路)直接覆盖重载的用途。

5. 核心原理之三:变长参数与「slice 内建函数」——双重虚构

书 5.4 节教可变参数:在最后一个参数后面加 ...,写成 fn print_numbers(numbers: ...i32),调用时随便塞参数; 然后又教了一个「slice 内建函数」,说可以 print_numbers(slice(numbers)) 把数组转成切片传进去6。

两处都是假的,而且假得有层次:

  • ...i32 这种变参语法,Zig 没有。真实 Zig 的可变参数靠切片—— 调用方把参数装进数组,函数收一个 []const i32。 切片(slice)指「一段连续元素的视图」,由指针加长度的组合—— 指针就是装着内存地址的变量,书在第 03 章 的类型表里出现过 []T,那个词条是真的,这里却没认出它正是答案。
  • slice(...) 这个「内建函数」不存在——Zig 的内建函数(以 @ 开头的那些, 如 @import、@sizeOf)里没有任何一个叫 slice 的。 给不存在的内建函数编一个用法,这是全书虚构里最胆大的一类: 不是借别家,是纯造。

6. 核心原理之四:递归——概念真,两个细节假

5.5 节教递归:阶乘、斐波那契、基例(base case,让递归停下来的那个简单情形) 与递归情形的结构、栈溢出(stack overflow,递归太深把调用栈用光导致崩溃)的警告7。 这些是跨语言成立的通用知识,例子逻辑(5! = 120、fib(10) = 55 一类的推演)都是对的。

两个细节要修:

  1. 书说尾递归(tail recursion,递归调用是函数最后一步操作的递归) 「可以被编译器优化成循环」7。真实 Zig 不保证尾调用优化—— 想写循环就该写循环,别把性能押在一个不保证的优化上。
  2. 例子的语法仍是借的:if n == 0 { ... } else { ... } 里 return 的用法 大体可用,但真实 Zig 更地道的递归函数常把 if 当表达式 (return if (n == 0) 1 else n * factorial(n - 1);)—— 这又是「if 是表达式」这个缺席主题的一次回响。

7. 核心原理之五:匿名函数与闭包——一半真,一半纯 Rust

书 5.6 节教「匿名函数(也叫 lambda)」:用 fn (x: i32) i32 { ... } 这个字面量赋给变量、当参数传给高阶函数(把函数当值的函数)、 还教了闭包(closure,捕获定义处外部变量的函数)8。

对账:

  书教的                            真实 Zig
  ─────────────────────────────────────────────────────────
  fn 字面量赋给变量                    函数是编译期实体,不能赋值给 var
  函数当参数传(apply(square, 5))     函数类型作参数类型是真的——但要写
                                     *const fn (i32) i32,且传的是具体函数
  闭包:捕获外部变量 x                  不存在。Zig 没有闭包;
                                     「函数+它要用的数据」要显式打包成结构体

  图说:高阶函数的思想是真的(函数可以当值传递的类型),闭包是假的。

「Zig 没有闭包」值得多说一句为什么:闭包要偷偷捕获环境,而 Zig 的信条是 没有隐藏的任何东西——没有隐藏控制流、没有隐藏分配、也没有隐藏捕获。 要用「函数加状态」,真实 Zig 让你声明一个结构体,把函数指针和数据并排放进去, 明明白白。这个「没有闭包」和第 13 章的「没有宏」是同一条设计底线。

8. 模块与导入导出:前半真,后半借

第六章前一半讲了真实成立的东西:一个 .zig 文件就是一个模块, 用 @import 引入,默认私有、pub 前缀才对外可见9——这三条是真实 Zig 的模块体系的骨架,也是全书模块部分唯一值得抄的段落。

后半开始借:

  • 「用 * 通配导入所有符号」——Zig 没有。真实 @import 永远引入整个模块 作为一个值(如 const mymodule = @import("mymodule.zig");), 用点号取用成员;没有「导入所有符号到当前名字空间」这回事。
  • mymodule.private_function as my_private_function 别名语法——不存在。 真实做法是用 const 起别名:const my_private_function = mymodule.private_function; (且私有符号本来就访问不到,书的例子自相矛盾:它别名了一个私有函数, 前面刚说它访问不到会编译失败)。
  • 「@import 后跟模块路径和符号名」——@import 只接一个字符串参数(文件路径), 不接符号名;书的描述混进了 Python from x import y 的形状。

9. 包管理:zig build -deps 与 zig.mod 的连环假

6.3 节教包管理,给了一条命令:

  zig build -deps std -o myproject

真实 Zig 的 zig build 没有 -deps 这个参数——依赖写在 build.zig.zon 里,由构建脚本消费10。这是全书第二次出现 zig.mod (第一次在第 02 章),这次书说在里面写 dep "std"10。 std 是标准库,随编译器发布,从来不是、也不可能是需要声明的外部依赖—— 这个细节单独就能判死刑:真用过 Zig 的人不会写出「把标准库声明为依赖」。

这一节也有真话:书承认「Zig 没有内置包管理器」, 并列出了真实的第三方工具 zigmod10——只是它把 zigmod 的 zig.mod 格式 当成了 Zig 本身的约定(第 02 章的病灶在这里找到病根)。

10. 作者的判断与证据

  • 经核对可用: fn 函数定义骨架、pub 可见性、@import 基本用法、 递归概念与全部例子逻辑、模块即文件的模型379。
  • 经核对不可用: 默认参数、函数重载(全部三种判据)、... 变参、slice() 内建、 闭包捕获、* 通配导入、as 别名、zig build -deps、dep "std"456810。
  • 证据形态: 全章无一条编译器输出、无一个可运行项目的链接;虚构的语法 密度与章节的「高级程度」正相关——越往后的节(重载、变参、闭包)假得越彻底, 这符合「生成器对常见概念有素材、对 Zig 的实际设计无知」的特征。

判断(我们的,不是书里的): 这一章的虚构不是随机的,有一条清晰的分界线: 「能在 C 系教材里找到的通用函数知识」全真,「Zig 区别于其他语言的函数设计」 全假(重载、默认参数、闭包恰好都是 Zig 明确拒绝的)。 如果错,会错在: 如果这本书对应的是某个 Fork 或非官方方言, 这些语法也许成立——但全书自称教的是 ziglang.org 的 Zig, 且附录 B 给的就是官方链接,这个辩解不成立。

11. 边界与局限

  • 书没讲 Zig 函数最要紧的三件事:错误联合返回类型(!T)、try/catch、 defer/errdefer——它们才是 Zig 函数章节的真实主角。
  • 书把「多返回值」教成裸元组语法,真实 Zig 返回多个值的标准做法是 返回一个匿名结构体(return .{ .quot = 3, .ok = true };, 字段名就是结构体里每个成员的名字)—— 两种写法都能达到目的,但后者每个值都标了名字,可读性差出一大截。
  • 模块章没讲 @import 的真实路径规则(相对当前文件找、按 build.zig 里写的 「模块名 → 文件」对应关系来认), 也没讲 usingnamespace 已被移除的版本变迁——它讲的「模块体系」 是一个停留在概念层的平均模型。

12. 逐条账目:书里的每个函数/模块机制,一步走查

前面几节按「机制」对账,这一节换一个颗粒度:拿书里自己给的例子, 一步不跳地走查,看每一行在真实 Zig 编译器面前会发生什么。 这是「这本书不能当教材用」最直接的证据形态。

12.1 默认参数 greet——三步全编

书 5.2 节的例子,想教「不传第二个参数就用默认值」:

fn greet(name: []const u8, greeting: []const u8 = "Hello") void { ... }
greet("Alice");                 // 书说: greeting 自动等于 "Hello"
  ① 编译函数定义        → 编译错误:greeting 的 "= "Hello"" 非法,
                            Zig 的参数没有默认值这个语法位
  ② 编译调用 greet("Alice") → 走不到:函数定义已经失败;
                            就算定义过关,少一个参数也是
                            「参数个数不符」的错误
  ③ 真实 Zig 的等价做法   → fn greet(name: []const u8, greeting: ?[]const u8) void
                            参数类型带个问号(可选类型,「可能是这个类型、
                            也可能没有」),函数体第一行写
                            const g = greeting orelse "Hello";
                            orelse: 可选值有就用、没有就换用后面的默认值

  图说:书教的是一种「声明时偷懒」的语法;真实 Zig 把这个懒
  拆成了「类型上标问号 + 体内补默认值」两步——懒还在,但明写着。

12.2 重载 add——编译器的「二义性地狱」模拟

书教同名 add 共三份:按参数类型(i32 版、f64 版)、按参数个数、按返回类型。 拿真实 C++ 的经验当镜子,你就能看出 Zig 为什么拒绝:

  场景                       C++ 里会发生什么(重载的代价)
  ────────────────────────────────────────────────────
  add(1, 2.5)              一个 int 一个 double,两边都能靠转换凑上,
                            C++ 要按「转换代价」裁决,规则几页纸;
                            裁决错了就得到一个 surprise
  add(1, 2) 你想要 f64 版    参数都对,返回类型不参与决议
                            (书教的第三种重载 C++ 也不允许——
                            书这里连 C++ 都讲错了)
  半年后读代码               光标停在 add 上,编辑器告诉你三个候选,
                            你要自己在脑子里跑一遍那几页纸的裁决规则

  图说:重载把「选哪个函数」从编译器手里塞回读者脑子里。
  Zig 的裁决规则只有一条:函数名不同。

12.3 变参 print_numbers——从调用到函数体四层全编

书 5.4 节的完整链条:fn print_numbers(numbers: ...i32), 调用 print_numbers(1, 2, 3, 4, 5); 又教 print_numbers(slice(numbers)) 把数组「转成切片」传进去6。

  ① 函数签名      `...i32` → 语法错误(Zig 无 ... 参数)
  ② 调用处的 1,2,3,4,5    → 语法层就接不住「任意多个参数」
  ③ 函数体 for (number in numbers)
                            → Zig 的 for 不长这样(第 04 章),
                            而且 in 这个关键字在 Zig 里不存在
  ④ slice(numbers)        → 没有这个内建函数;数组本来就能当切片用,
                            真实写法 print_numbers(&numbers)
                            (& 取地址,数组的地址自动看成一段切片)

  图说:四层每层一个来源——C 的 varargs 形状、JS 的 for-in、
  想象的内建函数。一层一层的借,凑成一条谁也没写过的语法。

12.4 递归 factorial——真的那一层,走一遍

为了公平,把书里真的能跑的例子也走一遍。5.5 节的阶乘 (书说 n=5 时答案是 120,这个数对):

  factorial(5)
  → n=5 不为 0,进入递归情形:返回 5 * factorial(4)
  → factorial(4) = 4 * factorial(3)
  → factorial(3) = 3 * factorial(2)
  → factorial(2) = 2 * factorial(1)
  → factorial(1) = 1 * factorial(0)
  → factorial(0) = 1            ← 基例:递归在这里停
  回溯相乘:1 → 1 → 2 → 6 → 24 → 120

  图说:每深入一层,调用栈(记录「我是谁调出来的、该回到哪」
  的那摞记录)加一层;深度 = n,所以 n 太大就会把这摞记录用光——
  这就是书警告的栈溢出。

这个例子的概念、数字、警告全对——它不需要任何 Zig 特有的机制。 再次印证那条分层规律:越通用的知识书越可信,越 Zig 的机制书越虚构。

12.5 模块导入——书内自相矛盾的一对例子

6.2 节有两个例子,前后脚出现,互相打架:

  例子一(教「通配导入」):书说用 * 导入所有符号,
      然后代码里写的却是 const mymodule = @import("mymodule");
      ——跟「通配导入」四个字毫无关系,就是普通导入
  例子二(教「别名」):mymodule.private_function as my_private_function;
      ——上一节刚说过 private_function 是私有的、别的模块访问不到
        「This will not compile」;这一节就把它别名过来用

  图说:两个例子都说明生成时上下文没保住——
  教 A 配的图是 B,讲私有之后的例子直接用私有。

真实 Zig 里这两个需求的正解都只有一行 const: const my = @import("mymodule.zig");(别名就是起个新常量名, 然后全程用 my.greet()——这其实就是书 6.4 节自己教的模块别名 @import("mymodule") as my_module 的正确形态; 而那个 as 写法本身也是借的,来自 Python 的 import-as)。

12.6 模块路径与别名——6.4 节的逐条对账

6.4 节教模块路径和别名,五个说法逐一走查11:

  书的说法                        真实 Zig
  ──────────────────────────────────────────────────────
  @import("myproject/mymodule")   真实 @import 只接相对当前文件的
   用「目录/文件名」当路径         路径或模块名;带目录的写法要配合
                                  build.zig 里的模块声明,不是裸写字符串
  @import("/path/to/...")         绝对路径导入不存在——导入永远以
   用绝对路径导入                  项目为根,绝对路径会让构建不可复现
  @import("mymodule") as my_module  没有 as;别名就是再起一个常量:
                                  const my_module = @import("mymodule.zig");
  别名用来「避免命名冲突」          真实动机更朴素:名字太长,起个短的
  「尽量用相对路径」让代码可移植    这条忠告本身是真的,可惜在假的语法上

  图说:五条里四条假、一条真——那条真的还是任何语言都成立的常识。

值得多看一眼的是 as 这个借词的来历:Python 的 import numpy as np 是数据科学圈曝光量最大的 import 写法,生成器把「别名」这个概念 直接绑到了 as 上,完全没考虑目标语言用什么表达—— 这是「素材库驱动写作」的微观证据:概念是真的,句法是随手抓的。

12.7 zigmod 的身世——被书当成官方的那个第三方工具

书把 zigmod 的包管理教得很具体(安装命令、zig.mod 格式、 zigmod install)10。把 zigmod 的真实时间线补上, 你就明白书为什么会在它上面翻车:

  2019–2021   zigmod 等社区包管理器活跃,官方包管理还没影,
              那时教 zig.mod 虽然不规范、情有可原
  2022–2023   官方开始建包管理(build.zig.zon 落地),社区工具退潮
  2024-10     本书出版——教的是已经退潮的 zig.mod,
              官方机制(build.zig.zon)一个字没提

  图说:书的知识截止点停在了社区时代,
  而它成书时官方时代已经开始两年了。

这个「知识截止点错位」是 AI 生成书的又一特征: 训练数据里 zigmod 的篇幅远多于官方包管理(后者太新、文档太少), 于是生成时旧素材压过了新事实——人类作者只要装一次最新 Zig 就能避免的事,生成流程一步都没做。

12.8 第五章为什么「浮肿」——章节级复读的解剖

第五章占全书约七分之一的字符,是第二名(第七章)的 1.6 倍。 这份浮肿不是内容多,是同一内容讲了多遍。抽样三处:

  5.1 节已经讲过       5.2 节原样再讲一遍
  ──────────────────────────────────────────────────
  「参数可以有默认值」   「You can specify default values
                        for function parameters」
  多返回值的 divide 例子  同一个 divide 例子再贴一次
  greet("Alice") 调用    greet("Alice") 再调用一次

  图说:两个相邻小节的内容重叠度肉眼可见;
  每节末尾的小结段也是同一句模板的换皮。

复读对读者无害(就是啰嗦),但它对鉴定极有价值: 人类作者重复自己,重复中总带点变形——第二次讲会换个例子、 补个第一次没想到的坑;生成器的重复是字节级的自我复制, 因为驱动它的是同一个「接下来该总结本节了」的模板信号。 第五章的浮肿比例,就是这份书稿里「模板段落」含量的上限估计。

12.9 一张总表:书教的函数观 vs 真实 Zig 的函数观

把全章的对账压成一张表,方便日后引用:

话题书教的真实 Zig书的来源
失败怎么表达返回值 + 布尔标记!T 错误联合,try/catch 强制处理C 时代
同名不同参函数重载(三种判据)不允许;用 comptime 泛型一函多用C++
参数可省略默认参数可选类型 ?T + orelseC++/Python
任意个参数...i32 变参切片参数 []const i32C
函数当值匿名 fn + 闭包捕获函数是编译期实体;闭包用「结构体+函数指针」显式打包Rust/JS
导入与别名* 通配、as 别名整模块导入成常量,const 起别名Python
项目清单zig.mod + zig build -depsbuild.zig.zon + build.zigzigmod(已过时)

12.10 真实 Zig 的「函数章」长什么样(补一份对照目录)

既然书的第五、六章教了 2.8 万字符的伪机制,把真实 Zig 函数与模块知识 的目录补在这里,读者按图去官方文档查即可:

  真实主题                       一句话内容                    官方文档的位置
  ──────────────────────────────────────────────────────────────────
  函数定义与调用                  书抄对了的那部分                语言参考 · Functions
  错误联合类型(!T)与错误集        「可能失败」写进类型;
                                try/catch/unreachable          语言参考 · Error Handling
  defer / errdefer              资源释放钉在返回前               概览 · Manual memory management
  可选参数(?T)与 orelse          书「默认参数」的正解             概览 · Optional type
  comptime 参数                  类型与值作为编译期参数            概览 · Generic data structures
  @import 与模块系统              文件即模块、包即依赖树            语言参考 · import
  build.zig 构建脚本             用 Zig 代码描述构建              官方文档 · Build System

这七条加起来,就是真实 Zig 里「函数与模块」的全部日常; 书的十六章里对应的部分,除了第一条外全部缺位—— 缺的每一条恰好都是 Zig 区别于 C++/Go/Rust 的地方。 这个「缺口的形状」比任何单条核对都更能说明问题: 书不是「没讲全」,而是「系统性地略过了 Zig 之所以是 Zig 的部分」。

12.11 高阶函数在真实 Zig 里的样子(补 5.6 节的对照)

书 5.6 节教「匿名函数当参数」(apply(square, 5)), 概念方向其实撞对了——真实 Zig 里函数确实可以作为参数类型, 只是写法要换个骨架8:

// 真实 Zig:参数类型是「指向函数的常量指针」
fn apply(f: *const fn (i32) i32, x: i32) i32 {
    return f(x);
}
fn square(x: i32) i32 { return x * x; }
// 调用:apply(square, 5) → 25

三个对照点:① 函数必须具名(square 是顶层函数,不是 fn 字面量); ② 参数类型里的 *const fn (i32) i32 是「函数指针」, 0.10 起 Zig 严格区分「函数体类型」和「函数指针类型」, 写错会直接编译错;③ 没有闭包——想携带状态,就定义一个 结构体把「数据 + 函数指针」并排装进去,调用处一目了然。 书教的闭包捕获(add_x 捕获外部 x)在这条路上一步都走不通, 因为 Zig 的函数根本没有「捕获」这个槽位——这也是第 05 章 判断块说的:闭包是这门语言明确拒绝的东西,不是漏了。

这一节的账目至此完整:书教的概念(函数当值传递)方向对、 书给的机制(fn 字面量赋值给 var、闭包捕获)全假、 真实的机制(具名函数 + 函数指针 + 结构体打包状态)三步替换。 三步替换是 Zig 社区的通行答案,在任何真实的 Zig 代码库里 都能找到实例——这正是「照书学不会、按社区惯例一学就会」的 又一典型案例。

12.12 本章账目的小结

第五、六两章合计 2.8 万字符,是全书的「重力中心」: 篇幅最大、虚构最密、借来的语言最多(C++/C#/Python/Go/Rust 各有贡献)。 但这两章也贡献了全书最干净的一句真话——6.3 节承认 「Zig 没有内置包管理器」——以及全书最有用的真机制之一:defer。 一半是海水一半是火焰,这句话就是整本书的缩影。 后面第 10 章会再见到这两章的遗留问题(zig.mod 在包管理一章复发), 第 11 章会见到它们埋下的泛型伏笔(fn swap[T] 在第 14 章长成整章虚构)。

最后一笔账:这两章在全书的地位,相当于「缝合模型的最大截面」—— 把任意一章拆开,你都能看到「概念真、机制假」的分层; 但只有第五、六章把五门语言的机制一次性全部请上了场。 读懂这两章的账目,其余各章都是它的缩小版。

这也是我们把最长的一章拆解放进全书靠前位置的原因: 主人若时间有限,只读 01(承诺)、05(最大截面)、14(总判)三章, 就能拿到这本书九成的账目;其余十一章是这三章的展开与证据。

(本章因此也是全拆解里唯一按「原书章字符」足额对账到 1.0 倍以上的章节—— 原书这两章的水分最重,我们的拆解必须足额覆盖,账目才算完整。)

13. 可带走的

  1. 函数定义骨架(fn、参数:类型、返回类型、void)可放心用。
  2. 记住三个「Zig 没有」:没有默认参数、没有重载、没有闭包——书这三节照抄自 C++/Rust。
  3. 「可能失败」在真实 Zig 里写进返回类型(!i32),不是书教的「值+布尔标记」。
  4. 默认参数的正解:参数类型标 ? 变成可选,函数体里用 orelse 补默认值。
  5. 递归的概念和例子逻辑通用;但别指望尾递归优化,Zig 不保证。
  6. 模块即文件、@import、pub 可见性——第六章前半是全书模块部分唯一可信的段落。
  7. zig build -deps、dep "std"、zig.mod——全是虚构;真实依赖写在 build.zig.zon。
  8. 书自己的模块例子互相矛盾(教通配配普通导入、刚说私有就拿私有做别名)——上下文没保住的生成故障。
  9. 想见识真 Zig 的函数章:读官方文档的「错误处理 + defer + 可选参数」三节,那才是这门语言的函数观。

14. 原文地图

主题原书章原文位置
函数定义、divide 多返回值例子Chapter 5: Functionstext/08-ch05-chapter-5-functions.txt:7(搜「blocks of code that can be reused」) · text/08-ch05-chapter-5-functions.txt:53(搜「Multiple Return Values」)
默认参数Chapter 5: Functionstext/08-ch05-chapter-5-functions.txt:75(搜「can have default values」) · text/08-ch05-chapter-5-functions.txt:139(搜「optional when calling」)
函数重载三种判据Chapter 5: Functionstext/08-ch05-chapter-5-functions.txt:197(搜「same name but different parameter lists」) · text/08-ch05-chapter-5-functions.txt:239(搜「based on their return type」)
... 变参与 slice 内建Chapter 5: Functionstext/08-ch05-chapter-5-functions.txt:269(搜「arbitrary number of arguments」) · text/08-ch05-chapter-5-functions.txt:303(搜「slice built-in function」)
递归、尾递归Chapter 5: Functionstext/08-ch05-chapter-5-functions.txt:327(搜「call themselves directly or indirectly」) · text/08-ch05-chapter-5-functions.txt:379(搜「Tail recursion」)
匿名函数与闭包Chapter 5: Functionstext/08-ch05-chapter-5-functions.txt:421(搜「not given a name」) · text/08-ch05-chapter-5-functions.txt:467(搜「capture variables from their surrounding scope」)
模块创建、pub 可见性Chapter 6: Modulestext/08-ch05-chapter-5-functions.txt:489(搜「Creating Modules」) · text/08-ch05-chapter-5-functions.txt:551(搜「Visibility of Symbols」)
通配导入与 as 别名Chapter 6: Modulestext/08-ch05-chapter-5-functions.txt:625(搜「* wildcard」) · text/08-ch05-chapter-5-functions.txt:645(搜「Aliasing Symbols」)
zig build -deps 与 zig.modChapter 6: Modulestext/08-ch05-chapter-5-functions.txt:675(搜「-deps flag」) · text/08-ch05-chapter-5-functions.txt:685(搜「Creating a zig.mod File」)
包管理与 zigmodChapter 6: Modulestext/08-ch05-chapter-5-functions.txt:731(搜「third-party package managers」)

Footnotes

  1. 出处:「Chapter 6: Modules」的正文位于 text/08-ch05-chapter-5-functions.txt 第 487–821 行(搜「Chapter 6: Modules」),EPUB 转码时未单独成文件;目录(text/02-ch04.txt:69,搜「Chapter 6: Modules」)有该章。 ↩

  2. 补充(不在书里,来自权威外部资料):「Errors are values, and may not be ignored」;try 是 catch |err| return err 的缩写;switch 用于错误时可强制穷举所有错误。来源:《Overview — Zig Programming Language》https://ziglang.org/learn/overview/(查阅于 2026-10-19)。 ↩

  3. 出处:「Chapter 5: Functions」第 5–87 段(text/08-ch05-chapter-5-functions.txt:7,搜「blocks of code that can be reused」;:13,搜「fn greet」;:39,搜「Returning Values」)。 ↩ ↩2

  4. 出处:「Chapter 5: Functions」第 75–85 段(text/08-ch05-chapter-5-functions.txt:75,搜「can have default values」;:81,搜「= "Hello"」)、第 137–155 段(text/08-ch05-chapter-5-functions.txt:137,搜「Default Parameters」)。真实 Zig 无默认参数。 ↩ ↩2

  5. 出处:「Chapter 5: Functions」第 195–265 段(text/08-ch05-chapter-5-functions.txt:197,搜「same name but different parameter lists」;:217,搜「number of arguments」;:239,搜「based on their return type」;:241,搜「While it's not recommended」)。 ↩ ↩2 ↩3

  6. 出处:「Chapter 5: Functions」第 267–323 段(text/08-ch05-chapter-5-functions.txt:269,搜「arbitrary number of arguments」;:273,搜「... syntax」;:303,搜「slice built-in function」)。...i32 与 slice() 均不存在于 Zig。 ↩ ↩2 ↩3

  7. 出处:「Chapter 5: Functions」第 325–417 段(text/08-ch05-chapter-5-functions.txt:327,搜「call themselves directly or indirectly」;:333,搜「factorial」;:353,搜「Base case」;:379,搜「Tail recursion」;:415,搜「stack overflow」)。 ↩ ↩2 ↩3

  8. 出处:「Chapter 5: Functions」第 419–485 段(text/08-ch05-chapter-5-functions.txt:421,搜「not given a name」;:431,搜「var add = fn」;:443,搜「higher-order functions」;:467,搜「capture variables from their surrounding scope」)。 ↩ ↩2 ↩3

  9. 出处:「Chapter 6: Modules」第 491–575 段(text/08-ch05-chapter-5-functions.txt:489,搜「Creating Modules」;:513,搜「@import keyword」;:551,搜「Visibility of Symbols」;:553,搜「pub keyword」)。 ↩ ↩2

  10. 出处:「Chapter 6: Modules」第 669–749 段(text/08-ch05-chapter-5-functions.txt:675,搜「-deps flag」;:681,搜「zig build -deps std」;:687,搜「zig.mod file to declare」;:695,搜「dep "std"」;:735,搜「zigmod」)。官方依赖声明文件是 build.zig.zon,见《Overview》https://ziglang.org/learn/overview/(查阅于 2026-10-19)。 ↩ ↩2 ↩3 ↩4 ↩5

  11. 出处:「Chapter 6: Modules」第 751–821 段(text/08-ch05-chapter-5-functions.txt:767,搜「@import("mymodule")」;:783,搜「Relative Paths」;:795,搜「Absolute Paths」;:807,搜「as my_module」;:815,搜「Use relative paths whenever possible」)。 ↩

On this page