From 2f880f067951d7d2835c4d4bd41de2fa81d42598 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 19 Apr 2026 20:58:15 +0800 Subject: [PATCH 1/9] feat(docs): support 0.16.0 --- course/.vitepress/sidebar.ts | 8 + course/.vitepress/theme/config.ts | 2 +- course/update/0.16.0-description.md | 363 ++++++++++++++ course/update/upgrade-0.16.0.md | 753 ++++++++++++++++++++++++++++ 4 files changed, 1125 insertions(+), 1 deletion(-) create mode 100644 course/update/0.16.0-description.md create mode 100644 course/update/upgrade-0.16.0.md diff --git a/course/.vitepress/sidebar.ts b/course/.vitepress/sidebar.ts index f6aa15b6..945ed777 100644 --- a/course/.vitepress/sidebar.ts +++ b/course/.vitepress/sidebar.ts @@ -220,6 +220,14 @@ export default [ text: "版本说明", collapsed: true, items: [ + { + text: "0.16.0 升级指南", + link: "/update/upgrade-0.16.0", + }, + { + text: "0.16.0 版本说明", + link: "/update/0.16.0-description", + }, { text: "0.15.1 升级指南", link: "/update/upgrade-0.15.1", diff --git a/course/.vitepress/theme/config.ts b/course/.vitepress/theme/config.ts index 49d49919..08af1f81 100644 --- a/course/.vitepress/theme/config.ts +++ b/course/.vitepress/theme/config.ts @@ -1,3 +1,3 @@ -const version: string = "0.15.1"; +const version: string = "0.16.0"; export { version }; diff --git a/course/update/0.16.0-description.md b/course/update/0.16.0-description.md new file mode 100644 index 00000000..e5bf8396 --- /dev/null +++ b/course/update/0.16.0-description.md @@ -0,0 +1,363 @@ +--- +outline: deep +comments: false +showVersion: false +--- + +# `0.16.0` + +2026/4/13,`0.16.0` 发布,历时 8 个月,有 244 位贡献者参与,一共进行了 1183 次提交! + +如果要用一句话概括这个版本,那就是:**`0.16.0` 把上一轮预告过的大量基础设施重构真正落地了。** + +`0.15.x` 还在为 `std.Io`、增量编译和新的工具链架构铺路,到了 `0.16.0`,这些方向已经进入可以大规模体验的阶段:I/O 统一为接口、`main` 可以直接拿到 `io` 和 `gpa`、增量编译进一步可用、新 ELF linker 开始接入默认流程,同时语言层也继续清理历史设计。 + +## 目标支持 + +`0.16.0` 在目标支持上的一个重要变化,是 Zig 对“哪些平台值得持续投入工程质量”这件事变得更明确了。 + +比较值得注意的点有: + +- `aarch64-freebsd`、`aarch64-netbsd`、`loongarch64-linux`、`powerpc64le-linux`、`s390x-linux`、`x86_64-freebsd`、`x86_64-netbsd`、`x86_64-openbsd` 这些目标现在都会在 Zig 的 CI 中原生测试 +- 新增 `aarch64-maccatalyst` 与 `x86_64-maccatalyst` 的交叉编译支持 +- 新增 `loongarch32-linux` 的初始支持,不过当前仍不支持 libc +- Alpha、KVX、MicroBlaze、OpenRISC、PA-RISC、SuperH 等架构加入了基础支持 +- Oracle Solaris、IBM AIX、z/OS 支持被移除;`illumos` 不受影响,仍然保留支持 +- 栈回溯支持进一步扩大,几乎所有主流目标在崩溃时都能得到更可靠的 stack trace + +对普通用户来说,这意味着 Zig 在常见 Linux / BSD / macOS / Windows 目标上的“可用性底线”又往前推了一步;而对于比较边缘的平台,官方也更清晰地区分了“支持”“实验性支持”和“不再支持”。 + +## 系统最低版本要求 + +| 操作系统(Operating System) | 最低版本要求(Minimum Version) | +| :--------------------------- | :-----------------------------: | +| DragonFly BSD | 6.0 | +| FreeBSD | 14.0 | +| Linux | 5.10 | +| NetBSD | 10.1 | +| OpenBSD | 7.8 | +| macOS | 13.0 | +| Windows | 10 | + +## 语言变动 + +### `switch` 继续补齐语义 + +`switch` 是这一轮里继续被打磨的语言特性之一。现在,`packed struct` 和 `packed union` 可以直接作为 prong item,比较规则按照 backing integer 来做;同时,decl literals、需要结果类型的表达式、union tag capture 等场景也获得了更一致的支持。 + +这类变更本身并不一定会让旧代码报错,但它明显减少了过去一些“语言明明应该支持、但实现上还没补齐”的边角问题。 + +### `@cImport` 正式进入“迁移期” + +`0.16.0` 仍然保留了 `@cImport`,但已经明确将其标记为 deprecated。官方方向是把 C 头文件翻译迁到构建系统中,通过 `build.zig` 里的 `addTranslateC` 生成模块,再在 Zig 代码里使用 `@import("c")`。 + +这和 Zig 未来“逐步把对 LLVM / Clang 的库级依赖转向进程级依赖”的方向是一致的。 + +同时,`translate-c` 的实现现在已经从 `libclang` 切换到了 Aro / translate-c 方案。对大多数用户来说这是透明的,但如果你升级后发现 C 头文件翻译行为有差异,它更可能是实现 bug,而不是新的预期行为。 + +### `@Type` 被拆分为多个独立内建函数 + +这是 `0.16.0` 最明显的语言级 breaking change 之一。`@Type` 被移除,原来依赖它造类型的元编程代码,需要迁移到新的内建函数: + +- `@EnumLiteral()` +- `@Int()` +- `@Tuple()` +- `@Pointer()` +- `@Fn()` +- `@Struct()` +- `@Union()` +- `@Enum()` + +这项改动的核心目标,是让“构造类型”这件事更直观,也让常见场景不必再绕一层 `std.meta.Int`、`std.meta.Tuple` 之类的辅助函数。 + +### packed / extern 相关规则更严格 + +这次发布继续收紧了位级布局和 ABI 边界的隐式行为: + +- `packed union` 现在要求更明确的 backing integer 语义 +- `packed struct` / `packed union` 不再允许直接放指针字段 +- `extern` 场景下,`enum` 与 `packed` 类型不能再依赖隐式推断的底层整数类型 + +从设计上看,这些限制的方向非常统一:**凡是会影响 ABI 或精确内存布局的内容,Zig 都更倾向于要求你显式写出来。** + +### 向量语义进一步收紧 + +`0.16.0` 禁止了运行时向量索引,同时也不再鼓励通过旧式内存强转在数组和向量之间来回转换。简单来说,向量更明确地被当成“值语义上的 SIMD 数据”,而不是“碰巧可以按数组方式随便访问的内存”。 + +另外,小整数类型在“绝对不会丢精度”的前提下,现在可以安全地隐式转换为浮点类型,这也让数值代码更顺手了一些。 + +### 类型解析与依赖环错误大幅重做 + +`0.16.0` 还重做了编译器内部的类型解析流程。这个改动的影响非常深: + +- 许多以前会误报 dependency loop 的代码现在可以正常工作 +- 增量编译和普通编译之间的一致性明显增强 +- 一小部分本来就存在真实依赖环的代码,现在会更早、更明确地报错 + +如果你升级后遇到以前没见过的 dependency loop,先别急着回退版本。因为 `0.16.0` 的错误报告已经能更清楚地指出环路是怎么形成的,通常只要打断其中一条依赖即可。 + +## 标准库 + +### I/O 作为 Interface + +这是 `0.16.0` 最重头的内容,没有之一。 + +从这个版本开始,所有输入输出相关能力都围绕 `std.Io` 展开。更准确地说,凡是可能阻塞控制流,或会引入非确定性的操作,都被纳入了 `Io` 的抽象边界内。 + +当前官方提供了几种典型实现: + +- `Io.Threaded`:基于线程,功能最完整,也是从 `0.15.x` 升级时最接近旧行为的实现 +- `Io.Evented`:仍在实验阶段,用来推动接口演进 +- `Io.failing`:用于模拟“不支持任何操作”的环境 + +围绕它,标准库引入了整套新的任务和并发抽象: + +- `Future` +- `Group` +- `Batch` +- `Select` +- `Queue(T)` +- 统一的 cancelation 模型 + +这不仅是 API 改名,而是 Zig 对“并发 I/O 应该怎样进入语言生态”给出的新答案。文件系统、网络、进程、同步原语、定时器等能力,都围绕这套接口重新组织了。 + +### `Juicy Main` + +为了配合新的 `std.Io`,`main` 也获得了一个很实用的新入口:`std.process.Init`。 + +只要把 `main` 写成: + +```zig +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; + const io = init.io; + _ = gpa; + _ = io; +} +``` + +你就能直接拿到: + +- `gpa` +- `io` +- `arena` +- `environ_map` +- `preopens` +- `minimal.args` / `minimal.environ` + +这让应用入口第一次真正成了“进程上下文的注入点”。对于应用开发来说,这个改动的体感甚至不亚于 `std.Io` 本身。 + +### 环境变量和进程参数不再是全局状态 + +和 `Juicy Main` 配套的另一项重要变化,是环境变量和进程参数都不再被鼓励当成全局状态来访问。 + +现在,环境变量原则上只存在于应用入口的 `Init` 里;需要使用它们的函数,应当显式接收需要的值,或者接收 `*const std.process.Environ.Map`。 + +这个方向很符合 Zig 一贯的设计哲学:尽量少依赖隐式的全局上下文,让副作用和依赖关系都显式体现在函数签名里。 + +### 线程与分配器模型继续更新 + +围绕新的 `std.Io`,标准库的并发相关设施也继续收敛: + +- `std.Thread.Pool` 被移除,官方建议迁移到 `std.Io.async` / `std.Io.Group.async` +- `std.heap.ArenaAllocator` 变成了 thread-safe 且 lock-free +- `std.heap.ThreadSafeAllocator` 被移除 + +如果把这些变化放在一起看,会发现 Zig 正在逐步放弃一些“靠包装器补线程安全”的旧路子,转而更偏向于:让真正需要并发的基础组件自己具备合适的并发语义。 + +### 文件系统、路径与容器 API 持续整理 + +除了 `std.Io` 大迁移之外,这次标准库还有很多看起来零碎、但真实影响升级体验的整理工作: + +- `std.io` 继续收敛到 `std.Io` +- `std.fs` 的一批常用入口迁到 `std.Io.Dir` / `std.Io.File` +- `std.process.getCwd*` 改名为 `currentPath*` +- `fs.path.relative` 变成纯函数,需要显式传入上下文 +- `File.Stat.atime` 变成可选值 +- `std.mem` 里 “index of” 系列统一更名为 “find” +- 一批容器继续向 unmanaged 方向迁移,`PriorityQueue` / `PriorityDequeue` 的命名也更统一了 + +这些调整单看都不算大新闻,但合在一起,就是一次很典型的 Zig 式“去历史包袱”整理。 + +### Windows 标准库实现继续下沉 + +Windows 也是 `0.16.0` 里非常有意思的一条线: + +- 网络 API 不再依赖 `ws2_32.dll`,而是直接基于 AFD 实现 +- 标准库继续向 NtDll 收敛 +- `std.Progress` 现在也支持 Windows 下的跨进程进度上报 + +这些工作虽然对多数用户不可见,但会真实影响程序的健壮性、性能,以及 cancelation / batch 模型在 Windows 上的完整度。 + +## 构建系统 + +### 依赖目录改到项目本地 `zig-pkg` + +从 `0.16.0` 开始,依赖包会被拉取到项目根目录旁边的 `zig-pkg` 目录,而不是继续使用过去那种全局解压缓存模式。 + +这个变化的好处很直接: + +- 你可以更方便地阅读、搜索、修改依赖源码 +- 可以更自然地把依赖目录换成本地 git clone +- IDE 也更容易直接索引整棵依赖树 + +### `zig build --fork` + +构建系统新增了 `--fork=[path]` 参数,可以让你临时用本地目录里的 fork 覆盖依赖树中的匹配包。 + +这对生态 breakage 的排查非常有帮助:你可以在不改版本元数据的前提下,直接调试一整串依赖之间的兼容问题。 + +### 依赖元数据更严格 + +`0.16.0` 还提高了 `build.zig.zon` 的要求: + +- 缺少 `fingerprint` 会直接失败 +- `name` 不能再用字符串,必须写成 enum literal +- 旧 hash 格式支持已被移除 + +这意味着旧项目在升级时,最好顺手检查一遍所有依赖元数据,而不是等到 `zig build` 报错再逐个补。 + +### 新增测试超时与错误输出样式 + +`zig build` 新增了几项很适合日常开发的参数: + +- `--test-timeout` +- `--error-style` +- `--multiline-errors` + +同时,旧的 `--prominent-compile-errors` 被移除了,对应的新写法是 `--error-style minimal`。 + +### 临时文件 API 被重构 + +`Build.makeTempPath` 和 `RemoveDir` step 都被清理掉了,新的推荐路径是: + +- `Build.addTempFiles` +- `Build.addMutateFiles` +- `Build.tmpPath` + +这项重构背后的核心思路,是把“临时目录”“可变文件”“缓存语义”这些东西从一开始就表达清楚,而不是让旧 API 在 configure 阶段偷偷做一堆文件系统副作用。 + +## Compiler + +### `translate-c` 改用 Aro + +编译器内部的 `translate-c` 现在基于 Aro / translate-c,而不是 `libclang`。这使 Zig 离“摆脱对 LLVM 的库级依赖”又近了一步。 + +对普通用户来说,这更多体现为长期方向上的信号:Zig 仍在持续拆除自己对 LLVM 的深绑定。 + +### 类型解析重构 + +前面在“语言变动”里提过,`0.16.0` 大幅重做了类型解析。这件事对编译器本身还有两个非常重要的连锁收益: + +- 依赖环报错更可解释 +- 增量编译和普通编译之间的一致性更强 + +这也是为什么你会发现,本版本许多看似分散的改动,最后都会回到“为了更可靠的增量编译”这个主题上。 + +### 增量编译继续前进 + +`0.16.0` 的增量编译已经比 `0.15.x` 实用得多: + +- 大多数场景下减少了“过度重编译” +- LLVM 后端也开始支持增量编译 +- ELF 目标在 `-fincremental` 下会默认启用新的 ELF linker +- 稳定性明显提升,虽然依然不是默认开启 + +官方现在明确鼓励大家实际使用: + +```sh +zig build -fincremental --watch +``` + +当然,它仍然有已知 bug,甚至可能包含误编译;所以这项功能在 `0.16.0` 里依然不是默认值。 + +### 后端进展 + +这一轮里: + +- x86 自托管后端修了 11 个 bug,仍然是 Debug 模式下的默认后端 +- aarch64 后端因为 `std.Io` 带来的标准库 churn 暂时放慢了节奏 +- Zig 的 WebAssembly 后端目前通过了 1813 / 1970(约 92%)项行为测试 + +## 链接器(Linker) + +### 新 ELF Linker + +`0.16.0` 的新 ELF linker 可以通过 `-fnew-linker` 显式启用,或者在 build 脚本里设置 `exe.use_new_linker = true`。更重要的是:**在 `-fincremental` 且目标是 ELF 时,它现在会默认启用。** + +官方给出的一个数据点非常直观:对 Zig 编译器本体做单行改动时,旧 linker 需要大约 `194ms`,而新 linker 只需要 `65ms`,几乎接近“完全跳过链接”的速度。 + +这也意味着过去那种专门暴露 `-Dno-bin`、只求快速拿到编译错误的工作流,收益已经没有以前那么明显了。 + +不过要注意,新 linker 目前还没完全补齐旧 linker / LLD 的能力,例如生成物还缺少 DWARF 信息。所以它已经够快、够值得试,但还没有到“所有场景都能无脑切换”的程度。 + +## Fuzzer(模糊测试器) + +### `Smith` 取代 `[]const u8` + +Fuzz 测试接口是 `0.16.0` 里另一个会直接影响用户代码的 breaking change。过去 fuzz test 习惯接收 `[]const u8` 输入;现在统一改成 `*std.testing.Smith`,由它来生成结构化值。 + +这套接口不仅更适合做复杂输入生成,还支持权重、范围、哈希相关性等能力,明显比过去的“原始字节切片”模式更强。 + +### 多进程、多核与 crash dump + +除了接口变化之外,fuzzer 本身也更强了: + +- 现在可以利用多核,受 `-j` 控制 +- 多个 fuzz test 会自动轮换并优先运行更“有产出”的测试 +- 崩溃输入会自动落盘,便于复现 + +## Bug 修复 + +本轮发布周期内,Zig 一共关闭了 345 个 bug 报告。 + +不过官方也直说了:**这个版本仍然包含已知 bug、误编译和回归问题。** 对于稍微复杂一点的项目,使用 `0.16.x` 仍然意味着要准备好参与 issue 反馈、最小复现和版本试验。 + +这并不意外。因为 `0.16.0` 本质上是一个“大迁移版本”,它把很多还在演进中的长期工程方向一次性推到了用户面前。 + +## 工具链(Toolchain) + +### LLVM 21 + +`0.16.0` 升级到了 LLVM `21.1.0`,覆盖了 Clang、libc++、libc++abi、libunwind、libtsan 等组件。 + +不过这里有一个非常值得注意的 caveat:为了规避 LLVM 上游的严重回归,Zig 在 `0.16.x` 中**完全禁用了 loop vectorization**。这会让某些代码生成结果比理想情况更保守,但它仍然比“在常见配置下误编译 Zig 编译器自身”要好得多。 + +官方预计这个性能回退不止会影响 `0.16.x`,甚至还会延续到 `0.17.x`,大概率要等到 `0.18.x` 才会彻底解决。 + +### libc 与系统头文件更新 + +这一轮工具链同时带来了: + +- `musl 1.2.5`(附带安全修复回移) +- `glibc 2.43` +- Linux `6.19` headers +- macOS `26.4` headers +- FreeBSD `15.0` libc +- 交叉编译时支持动态链接的 OpenBSD libc + +### `zig libc` 继续扩张 + +`zig libc` 继续吞并原来来自 musl、MinGW-w64、WASI libc 的一部分 C 实现。`0.16.0` 中,随 Zig 分发的 C 源文件总数从 `2270` 降到了 `1873`,减少了约 `17%`。 + +这里尤其值得一提的是:很多数学函数,以及 `malloc` 相关函数,现在都已经进入 `zig libc` 的实现范围。 + +### `zig cc` + +`zig cc` / `zig c++` 现在基于 Clang `21.1.8`。这意味着 Zig 在“C / C++ 工具链外壳”这条线上,也继续和整体 LLVM 版本一同推进。 + +## 路线图(Roadmap) + +官方对 `0.17.0` 的规划很明确:这是一个相对短周期版本,主要目标是升级到 LLVM `22`,并完成“把构建执行阶段和 `build.zig` 配置阶段分离”的工作。 + +在这之后,更长期的大方向仍然是: + +- 继续完成并稳定语言本身 +- 做完 aarch64 后端,并让它成为 Debug 模式默认后端 +- 继续增强链接器,减少对 LLD 的依赖,并服务增量编译 +- 继续增强内置 fuzzer +- 继续把对 LLVM 的依赖,从“链接库”转向“调用 Clang 进程” + +如果说 `0.15.x` 还是“这些方向马上就要影响到你了”,那 `0.16.0` 就是“它们已经开始真正影响你的日常开发了”。 diff --git a/course/update/upgrade-0.16.0.md b/course/update/upgrade-0.16.0.md new file mode 100644 index 00000000..f69bdd5a --- /dev/null +++ b/course/update/upgrade-0.16.0.md @@ -0,0 +1,753 @@ +--- +outline: deep +showVersion: false +--- + +本篇文档将介绍如何从 `0.15.1` 版本升级到 `0.16.0`。 + +`0.16.0` 是 Zig 近几个版本里破坏性变更最集中的一次更新之一。最大的主题是:**I/O 被统一到了新的 `std.Io` 接口**,同时语言层也清理了 `@Type`、`@cImport`、`packed` / `extern` 类型规则,以及一批历史 API。 + +## 语言变动 + +### `switch` 能力继续增强 + +`0.16.0` 继续补齐了 `switch` 的一些语义细节。最直观的一点是:`packed struct` 和 `packed union` 现在也可以直接作为 prong item 使用,并且比较规则基于它们的 backing integer。 + +此外,下面这些行为也得到增强或修复: + +- 需要结果类型的表达式(例如 `@enumFromInt`)可以直接写进 `switch` prong +- 联合类型的 tag capture 不再只限于 `inline` prong +- 对 `void` 做 `switch` 时,不再一律强制要求 `else` +- 一些错误集合和 one-possible-value 类型上的历史问题被修复了 + +这部分通常不需要你主动迁移,但如果你有比较复杂的 `switch` 写法,可以顺便回头看一下是否能更简洁。 + +### `@cImport` 开始迁移到构建系统 + +`@cImport` 在 `0.16.0` 里还没有被移除,但已经被正式标记为 deprecated。官方建议开始把 C 头文件翻译工作迁移到 `build.zig` 中,通过 `addTranslateC` 生成模块,再在 Zig 代码里 `@import("c")`。 + +旧写法: + +```zig +pub const c = @cImport({ + @cInclude("stdio.h"); + @cInclude("math.h"); + @cInclude("stdlib.h"); +}); + +const c = @import("c.zig").c; +``` + +新写法: + +`c.h` + +```c +#include +#include +#include +``` + +`build.zig` + +```zig +const translate_c = b.addTranslateC(.{ + .root_source_file = b.path("src/c.h"), + .target = target, + .optimize = optimize, +}); + +const exe = b.addExecutable(.{ + .name = "example", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ + .name = "c", + .module = translate_c.createModule(), + }, + }, + }), +}); +``` + +```zig +const c = @import("c"); +``` + +如果你升级到 `0.16.0` 后发现同一份 C 头文件翻译结果和以前不一致,也不用急着怀疑自己。因为 `translate-c` 的底层实现已经从 `libclang` 切换到了 Aro,这类差异更应该视为 bug 并反馈给 Zig。 + +### `@Type` 被拆分为独立的类型构造内建 + +这是 `0.16.0` 最重要的语言级 breaking change 之一。`@Type` 被移除了,原本依赖 `@Type(.{ ... })` 或 `std.meta.*` 造类型的代码,需要迁移到新的内建函数。 + +新增的核心内建包括: + +- `@EnumLiteral()` +- `@Int()` +- `@Tuple()` +- `@Pointer()` +- `@Fn()` +- `@Struct()` +- `@Union()` +- `@Enum()` + +常见迁移: + +```zig +@Type(.enum_literal) +``` + +⬇️ + +```zig +@EnumLiteral() +``` + +```zig +@Type(.{ .int = .{ .signedness = .unsigned, .bits = 10 } }) +``` + +⬇️ + +```zig +@Int(.unsigned, 10) +``` + +```zig +std.meta.Tuple(&.{ u32, [2]f64 }) +``` + +⬇️ + +```zig +@Tuple(&.{ u32, [2]f64 }) +``` + +如果你的项目大量依赖元编程,这一项往往是升级时最先爆出来的报错来源。建议先全局搜索 `@Type(` 和 `std.meta.`,再逐个迁移。 + +### 小整数类型现在可以安全地隐式转换为浮点 + +如果某个整数类型的所有可能值,都能被目标浮点类型精确表示,那么现在可以直接发生隐式 coercion。 + +旧写法: + +```zig +var foo_int: u24 = 123; +var foo_float: f32 = @floatFromInt(foo_int); +``` + +新写法: + +```zig +var foo_int: u24 = 123; +var foo_float: f32 = foo_int; +``` + +注意这只适用于“不会丢精度”的情况。像 `u25 -> f32` 这种仍然需要显式写 `@floatFromInt`。 + +### 运行时向量索引被禁止 + +此前很多人会把向量当成“可在运行时索引的数组”来用。`0.16.0` 不再允许这种写法。 + +旧写法: + +```zig +for (0..vector_len) |i| { + _ = vector[i]; +} +``` + +新写法: + +```zig +const vector_type = @typeInfo(@TypeOf(vector)).vector; +const array: [vector_type.len]vector_type.child = vector; + +for (&array) |elem| { + _ = elem; +} +``` + +如果你确实需要逐项遍历向量,请先把它显式 coercion 成数组,再做索引或遍历。 + +### 数组与向量不再支持旧式内存强转 + +`0.16.0` 不再鼓励通过 `@ptrCast` 在数组内存和向量内存之间来回转换。如果你之前是在做同构数据的值级转换,请直接使用 coercion: + +```zig +const arr: [4]i32 = .{ 1, 2, 3, 4 }; +const vec: @Vector(4, i32) = arr; +const back: [4]i32 = vec; +``` + +如果你的类型外层还包了一层 `error!` 或其他容器类型,先解包,再在内部做数组和向量之间的转换。 + +### 不再允许返回局部变量地址 + +下面这种初学者常见错误,现在会直接给出明确的编译错误: + +```zig +fn foo() *i32 { + var x: i32 = 1234; + return &x; +} +``` + +如果你在升级后遇到这类错误,正确修复方式通常是三种之一: + +- 直接返回值,而不是返回指针 +- 让调用方传入 buffer / 输出参数 +- 改用堆分配,并明确约定释放责任 + +### `packed` 与 `extern` 规则更严格 + +#### `packed union` 需要明确 backing integer,并保证各字段 bit size 一致 + +以前 Zig 对 `packed union` 的位级布局有一些隐式推断。`0.16.0` 开始要求它更明确。 + +旧写法: + +```zig +const U = packed union { + x: u8, + y: u16, +}; +``` + +新写法: + +```zig +const U = packed union(u16) { + x: packed struct(u16) { + data: u8, + padding: u8 = 0, + }, + y: u16, +}; +``` + +总结一下这条规则:如果你需要 `packed union`,就请明确写出 backing integer,并保证每个字段都能映射到同样大小的位表示。 + +#### `packed struct` / `packed union` 不再允许指针字段 + +如果你过去把指针直接塞进 `packed` 类型里,现在需要改成整数保存地址: + +```zig +const addr: usize = @intFromPtr(ptr); +const ptr_again: *T = @ptrFromInt(addr); +``` + +这项变更的核心原因是:很多目标平台里,指针并不只是“裸地址位”,而 `packed` 类型又承诺了精确的位级布局,因此两者不再兼容。 + +#### `extern` 场景下必须显式指定 tag type / backing type + +`enum`、`packed struct`、`packed union` 只要被用于 `extern` / `export` 场景,就不能再依赖隐式推断的底层整数类型。 + +旧写法: + +```zig +const Enum = enum { a, b, c, d }; +const PackedStruct = packed struct { a: u4, b: u4 }; +const PackedUnion = packed union { a: u8, b: i8 }; +``` + +新写法: + +```zig +const Enum = enum(u8) { a, b, c, d }; +const PackedStruct = packed struct(u8) { a: u4, b: u4 }; +const PackedUnion = packed union(u8) { a: u8, b: i8 }; +``` + +如果你的类型需要跨 ABI 边界导出,请把 tag type 或 backing type 明确写出来,不要再依赖编译器推断。 + +### 浮点取整内建现在可以直接产出整数 + +`@floor`、`@ceil`、`@round`、`@trunc` 现在可以直接把浮点值转成整数值。 + +旧写法: + +```zig +const x: i32 = @intFromFloat(@round(value)); +``` + +新写法: + +```zig +const x: i32 = @round(value); +``` + +这项改动本身不一定会让旧代码报错,但会让很多“先取整、再转整数”的写法明显简化。 + +### 类型解析规则被重做,依赖环错误会更清晰 + +`0.16.0` 彻底重做了编译器内部的类型解析流程。结果是: + +- 大多数以前能工作的代码会继续工作 +- 一些以前会莫名其妙报 dependency loop 的代码,现在反而能正常工作 +- 也有一小部分旧代码会因为真正的依赖环而在 `0.16.0` 开始报错 + +最常见的症状,是结构体默认字段值、对 `@This()` 的对齐查询、`@typeInfo` 反射等路径互相依赖。 + +这类问题没有统一的机械式修法,但 `0.16.0` 的错误信息比以前清楚很多,通常你只需要打断这条环上的任意一条依赖即可。 + +## 标准库 + +### `std.Io` 成为新的核心 I/O 抽象 + +这是 `0.16.0` 最大的标准库变更。现在,凡是“可能阻塞控制流”或“会引入非确定性”的能力,基本都要求显式接收一个 `std.Io` 实例。 + +这意味着下面这些领域都围绕 `std.Io` 重新组织了: + +- 文件系统 +- 网络 +- 进程 +- 同步原语 +- 定时器与睡眠 +- 一部分流式读写接口 + +对于从 `0.15.x` 升级的项目来说,如果你只是想先获得和以前类似的行为,通常可以从 `Io.Threaded` 开始: + +```zig +var threaded: std.Io.Threaded = .init_single_threaded; +const io = threaded.io(); +``` + +但这只是临时过渡方案。更理想的做法,仍然是把 `io: std.Io` 作为参数一路向下传递,或者放到你的应用上下文里统一管理。 + +测试代码则建议优先使用 `std.testing.io`。 + +### `main` 现在可以直接接收 `std.process.Init` + +`0.16.0` 给 `main` 函数增加了一个非常实用的新入口。你可以直接在参数里拿到已经初始化好的常用资源: + +- `init.gpa` +- `init.io` +- `init.arena` +- `init.environ_map` +- `init.preopens` + +新写法示例: + +```zig +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + const io = init.io; + try std.Io.File.stdout().writeStreamingAll(io, "Hello, world!\n"); +} +``` + +`main` 现在有三种合法形态: + +- `pub fn main() ...`:仍然合法,但拿不到参数和环境变量 +- `pub fn main(init: std.process.Init.Minimal) ...`:只拿到原始 `argv` / `environ` +- `pub fn main(init: std.process.Init) ...`:拿到完整预初始化资源 + +### 环境变量与命令行参数不再是全局状态 + +`0.16.0` 之后,环境变量和进程参数被明确收敛到 `main` 的初始化参数里,不再鼓励像以前一样把它们当作“随处可取的全局状态”。 + +读取参数: + +```zig +const std = @import("std"); + +pub fn main(init: std.process.Init.Minimal) void { + var args = init.args.iterate(); + while (args.next()) |arg| { + std.log.info("arg: {s}", .{arg}); + } +} +``` + +读取环境变量: + +```zig +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + for (init.environ_map.keys(), init.environ_map.values()) |key, value| { + std.log.info("env: {s}={s}", .{ key, value }); + } +} +``` + +如果你的库函数仍然需要环境变量,请改成显式传参,或者显式接收 `*const std.process.Environ.Map`。 + +### 进程 API 的入口被重新整理 + +围绕新的 `std.Io`,进程相关 API 也发生了明显变化。 + +启动子进程: + +```zig +var child = std.process.Child.init(argv, gpa); +child.stdin_behavior = .Pipe; +child.stdout_behavior = .Pipe; +child.stderr_behavior = .Pipe; +try child.spawn(io); +``` + +⬇️ + +```zig +var child = try std.process.spawn(io, .{ + .argv = argv, + .stdin = .pipe, + .stdout = .pipe, + .stderr = .pipe, +}); +``` + +运行并捕获输出: + +```zig +const result = try std.process.run(allocator, io, .{ + .argv = argv, +}); +``` + +替换当前进程镜像: + +```zig +const err = std.process.replace(io, .{ .argv = argv }); +``` + +### `std.Thread.Pool` 被移除 + +`std.Thread.Pool` 已经从标准库中移除。最常见的迁移方向,是改用 `std.Io.async` 或 `std.Io.Group.async`。 + +如果你过去用的是“提交一组任务,然后等待全部结束”的模式,通常可以这样迁移: + +```zig +fn doAllTheWork(io: std.Io) !void { + var group: std.Io.Group = .init; + errdefer group.cancel(io); + + group.async(io, doSomeWork, .{ io, &group, first_work_item }); + try group.await(io); +} +``` + +另外要特别注意:如果你的旧代码里除了 `Thread.Pool` 之外,还用了 `Thread.Mutex`、`Thread.Condition`、`Thread.ResetEvent` 等同步原语,那么升级到 `std.Io` 时,它们也应该一起迁到对应的 `std.Io.*` 类型。 + +### `std.io` 进一步收敛到 `std.Io` + +这一轮更新里,`GenericReader`、`AnyReader`、`FixedBufferStream` 等历史接口继续退出。 + +常见映射: + +- `std.io` ➡️ `std.Io` +- `std.Io.GenericReader` ➡️ `std.Io.Reader` +- `std.Io.AnyReader` ➡️ `std.Io.Reader` +- `std.leb.readUleb128` ➡️ `std.Io.Reader.takeLeb128` +- `std.leb.readIleb128` ➡️ `std.Io.Reader.takeLeb128` + +读取固定缓冲区: + +```zig +var fbs = std.io.fixedBufferStream(data); +const reader = fbs.reader(); +``` + +⬇️ + +```zig +var reader: std.Io.Reader = .fixed(data); +``` + +写入固定缓冲区: + +```zig +var fbs = std.io.fixedBufferStream(buffer); +const writer = fbs.writer(); +``` + +⬇️ + +```zig +var writer: std.Io.Writer = .fixed(buffer); +``` + +### 文件系统和路径 API 有一批实用迁移点 + +#### `readFileAlloc` + +旧写法: + +```zig +const contents = try std.fs.cwd().readFileAlloc(allocator, file_name, 1234); +``` + +新写法: + +```zig +const contents = try std.Io.Dir.cwd().readFileAlloc(io, file_name, allocator, .limited(1234)); +``` + +注意新的限制语义更严格:到达上限本身也会报错,错误名也从 `FileTooBig` 变成了 `StreamTooLong`。 + +#### `readToEndAlloc` + +旧写法: + +```zig +const contents = try file.readToEndAlloc(allocator, 1234); +``` + +新写法: + +```zig +var file_reader = file.reader(&.{}); +const contents = try file_reader.interface.allocRemaining(allocator, .limited(1234)); +``` + +#### 当前目录 API 更名 + +旧写法: + +```zig +std.process.getCwd(buffer) +std.process.getCwdAlloc(allocator) +``` + +新写法: + +```zig +std.process.currentPath(io, buffer) +std.process.currentPathAlloc(io, allocator) +``` + +#### `fs.path.relative` 变成纯函数 + +旧写法: + +```zig +const relative = try std.fs.path.relative(gpa, from, to); +defer gpa.free(relative); +``` + +新写法: + +```zig +const cwd_path = try std.process.currentPathAlloc(io, gpa); +defer gpa.free(cwd_path); + +const relative = try std.fs.path.relative(gpa, cwd_path, environ_map, from, to); +defer gpa.free(relative); +``` + +也就是说,`relative` 不再自己偷偷读取当前工作目录和环境变量,而是要求你把这些上下文显式传进去。 + +#### `File.Stat.atime` 现在是可选值 + +读取访问时间: + +```zig +stat.atime +``` + +⬇️ + +```zig +stat.atime orelse return error.FileAccessTimeUnavailable +``` + +设置时间戳: + +```zig +try file.setTimestamps(io, src_stat.atime, src_stat.mtime); +``` + +⬇️ + +```zig +try file.setTimestamps(io, .{ + .access_timestamp = .init(src_stat.atime), + .modify_timestamp = .init(src_stat.mtime), +}); +``` + +#### 其他值得顺手处理的文件系统改动 + +- `std.fs.wasi.Preopens` ➡️ `std.process.Preopens` +- 原来的 `atomicFile` 流程重构为 `createFileAtomic` +- `fs.getAppDataDir` 已被移除,应用应自行决定“应用数据目录”的策略 + +### `std.posix` 和 `std.os.windows` 的中层 API 被移除 + +这次标准库很明确地砍掉了很多“中不溜”的系统接口。如果你升级后是在 `std.posix` 或 `std.os.windows` 里踩雷,官方建议只选两条路: + +- 往上走,改用 `std.Io` +- 往下走,直接使用 `std.posix.system` + +也就是说,Zig 不再想长期维护那批半高层、半底层的历史包装函数。 + +### `std.mem` 的 “index of” 系列统一更名为 “find” + +`std.mem` 现在统一使用 `find` 作为“查找子串位置”的概念名称,并新增了 `cut`、`cutPrefix`、`cutSuffix`、`cutScalar`、`cutLast`、`cutLastScalar` 等函数。 + +如果你项目里大量用了 `indexOf` / `lastIndexOf` / `indexOfScalar` 这类 API,可以统一按新的 `find*` 命名规则做搜索替换。 + +### 容器继续向 unmanaged 方向收敛 + +这部分延续了 `0.14` 和 `0.15` 的趋势:标准库越来越倾向于“容器本身不持有 allocator,把 allocator 显式传给需要分配的方法”。 + +这次比较关键的变化有: + +- `ArrayHashMap`、`AutoArrayHashMap`、`StringArrayHashMap` 被移除 +- `AutoArrayHashMapUnmanaged` ➡️ `std.array_hash_map.Auto` +- `StringArrayHashMapUnmanaged` ➡️ `std.array_hash_map.String` +- `ArrayHashMapUnmanaged` ➡️ `std.array_hash_map.Custom` +- `PriorityQueue` 和 `PriorityDequeue` 都继续往 `.empty` / `push` / `pop` 风格迁移 + +`PriorityQueue` 常见重命名: + +- `init` ➡️ `initContext` +- `add` ➡️ `push` +- `addUnchecked` ➡️ `pushUnchecked` +- `addSlice` ➡️ `pushSlice` +- `remove` ➡️ `pop` +- `removeOrNull` ➡️ `pop` +- `removeIndex` ➡️ `popIndex` + +`PriorityDequeue` 常见重命名: + +- `init` ➡️ `.empty` +- `add` ➡️ `push` +- `addSlice` ➡️ `pushSlice` +- `addUnchecked` ➡️ `pushUnchecked` +- `removeMinOrNull` / `removeMin` ➡️ `popMin` +- `removeMaxOrNull` / `removeMax` ➡️ `popMax` +- `removeIndex` ➡️ `popIndex` + +### 分配器与并发模型继续调整 + +有两条需要直接注意: + +- `std.heap.ArenaAllocator` 现在变成了 thread-safe 且 lock-free +- `std.heap.ThreadSafeAllocator` 被移除 + +如果你的旧代码是“在外层包一层 `ThreadSafeAllocator`”,现在应改为直接选用本身适合并发场景的 allocator,或者改造调用结构,避免再依赖这层包装器。 + +### Fuzz 测试接口改成 `std.testing.Smith` + +如果你的项目用到了 fuzz 测试,这也是一个直接的 breaking change。 + +旧写法: + +```zig +const std = @import("std"); + +fn fuzzTest(_: void, input: []const u8) !void { + var sum: u64 = 0; + for (input) |b| sum += b; + try std.testing.expect(sum != 1234); +} +``` + +新写法: + +```zig +const std = @import("std"); + +fn fuzzTest(_: void, smith: *std.testing.Smith) !void { + var sum: u64 = 0; + while (!smith.eosWeightedSimple(7, 1)) { + sum += smith.value(u8); + } + try std.testing.expect(sum != 1234); +} +``` + +同时,`0.16.0` 的 fuzzer 还支持了多进程、多核利用和崩溃输入落盘。如果你本来就依赖 fuzz 测试,这次升级是值得顺手把整套流程一起更新的。 + +## 构建系统 + +### 依赖目录改到项目本地 `zig-pkg` + +从 `0.16.0` 开始,依赖包不再解压到全局缓存目录下,而是会被拉到项目根目录旁边的 `zig-pkg` 目录中。 + +这带来两个直接结果: + +- 调试依赖更方便,能直接 grep、编辑、替换 +- 你通常不应该把 `zig-pkg` 提交进仓库,但如果团队确实想这么做,也不是不允许 + +### `build.zig.zon` 的依赖信息更严格 + +这次升级后,如果依赖缺少 `fingerprint`,或者 `name` 还在用字符串而不是 enum literal,`zig build` 会直接失败。 + +另外,旧的 hash 格式也已经不再支持。也就是说,`0.15.x` 时还能勉强工作的某些老 `build.zig.zon`,到 `0.16.0` 可能需要顺手全部更新一遍。 + +### 可以通过 `zig build --fork` 临时覆写依赖 + +新加入的 `--fork=[path]` 允许你在不改 `build.zig.zon` 的前提下,临时把整棵依赖树中的某个包替换成本地目录里的 fork。 + +```sh +zig build --fork=/path/to/your-package +``` + +这对排查生态 breakage、联调依赖、离线开发都很有帮助。 + +### 新的错误输出与测试超时选项 + +`0.16.0` 的 `zig build` 新增或调整了这些常用参数: + +- `--test-timeout`:为每个 Zig 单元测试设置超时 +- `--error-style`:控制构建错误输出样式 +- `--multiline-errors`:控制多行错误信息展示方式 + +其中,旧的 `--prominent-compile-errors` 已被移除。对应的新写法是: + +```sh +zig build --error-style minimal +``` + +如果你平时配合 `--watch` 或增量编译工作流,`verbose_clear` / `minimal_clear` 这两种 error style 会比较顺手。 + +### 临时文件 API 被重构 + +这次构建系统还清理了旧的临时目录 API: + +- `Build.makeTempPath` 被移除 +- `RemoveDir` step 被移除 + +迁移方向是: + +- 用 `Build.addTempFiles` 创建非缓存的临时文件目录 +- 用 `Build.addMutateFiles` 表达“会修改文件”的流程 +- 用 `Build.tmpPath` 作为便捷入口 + +如果你以前是在 configure 阶段预先创建临时目录,再在 make 阶段清理,`0.16.0` 之后应该把这套逻辑迁到新的 `WriteFile` / temp files API 上。 + +### `builtin.subsystem` 被移除,`Target.SubSystem` 也迁了位置 + +如果你的代码依赖 `std.builtin.subsystem`,现在需要重新设计:真正的 subsystem 直到链接阶段才知道,编译期再去猜它并不可靠。 + +另外,`std.Target.SubSystem` 被移动到了 `std.zig.Subsystem`。旧名字目前仍有 deprecated alias,可暂时过渡,但新代码最好直接跟着新命名走。 + +### 增量编译与新 ELF linker 继续前进 + +`0.16.0` 里,增量编译已经明显比 `0.15.x` 更实用了: + +- LLVM 后端也开始支持增量编译 +- 在 ELF 目标上,`-fincremental` 会默认启用新的 ELF linker +- 对很多项目来说,`-Dno-bin` 的收益已经不再明显 + +常见工作流现在可以直接写成: + +```sh +zig build -fincremental --watch +``` + +不过也要注意: + +- 增量编译依然不是默认开启 +- 依然存在已知 bug 和误编译 +- 新 ELF linker 还不完整,例如目前生成物仍然缺少 DWARF 信息 + +换句话说,`0.16.0` 的增量编译已经值得日常试用,但如果你碰到诡异问题,仍要记得先排除它。 From 03b867452a057dfbefb5d284bfc765016012273e Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sat, 2 May 2026 01:15:14 +0800 Subject: [PATCH 2/9] Refactor code structure for improved readability and maintainability --- course/update/0.16.0-description.md | 482 +++++++++++++-- course/update/upgrade-0.16.0.md | 921 +++++++++++++++++++++++++++- 2 files changed, 1334 insertions(+), 69 deletions(-) diff --git a/course/update/0.16.0-description.md b/course/update/0.16.0-description.md index e5bf8396..542bdf06 100644 --- a/course/update/0.16.0-description.md +++ b/course/update/0.16.0-description.md @@ -18,15 +18,36 @@ showVersion: false 比较值得注意的点有: -- `aarch64-freebsd`、`aarch64-netbsd`、`loongarch64-linux`、`powerpc64le-linux`、`s390x-linux`、`x86_64-freebsd`、`x86_64-netbsd`、`x86_64-openbsd` 这些目标现在都会在 Zig 的 CI 中原生测试 -- 新增 `aarch64-maccatalyst` 与 `x86_64-maccatalyst` 的交叉编译支持 -- 新增 `loongarch32-linux` 的初始支持,不过当前仍不支持 libc -- Alpha、KVX、MicroBlaze、OpenRISC、PA-RISC、SuperH 等架构加入了基础支持 -- Oracle Solaris、IBM AIX、z/OS 支持被移除;`illumos` 不受影响,仍然保留支持 -- 栈回溯支持进一步扩大,几乎所有主流目标在崩溃时都能得到更可靠的 stack trace +- `aarch64-freebsd`、`aarch64-netbsd`、`loongarch64-linux`、`powerpc64le-linux`、`s390x-linux`、`x86_64-freebsd`、`x86_64-netbsd`、`x86_64-openbsd` 这些目标现在都会在 Zig 的 CI 中原生测试(OSUOSL 提供了 AArch64 和 Power ISA 硬件,IBM 提供了 z/Architecture 硬件) +- 新增 `aarch64-maccatalyst` 与 `x86_64-maccatalyst` 的交叉编译支持。这部分支持几乎是“免费”得到的,因为 Zig 自带的 `libSystem.tbd` 本来就提供了对应符号 +- 新增 `loongarch32-linux` 的初始支持,不过当前仍不支持 libc,LLVM 也仍把这个目标的 ABI 视为不稳定,只有走 `std.os.linux` 的纯 syscall 程序才能跑 +- Alpha、KVX、MicroBlaze、OpenRISC、PA-RISC、SuperH 等架构加入了基础支持。这些目标当前要求使用 Zig 的 C 后端(配合 GCC),或者外部的 LLVM/Clang fork +- Oracle Solaris、IBM AIX、z/OS 支持被移除——这些专有系统取系统头文件本身就不容易,影响了贡献的可审计性;`illumos` 是开源的 OpenSolaris fork,不受影响,仍然保留支持 +- 栈回溯支持进一步扩大,几乎所有主流目标在崩溃或使用 `DebugAllocator` 时都能得到更可靠的 stack trace +- 一批主要影响弱内存序架构(AArch64 尤其在没有 LSE 的情况下、LoongArch、Power ISA)以及不常见 page size 的标准库 bug 被修复 +- 影响 big-endian 主机的若干标准库与编译器 bug 被修复;big-endian ARM 目标在 ARMv6+ 下现在会输出 BE8 目标文件,而不是过去那套 BE32 对普通用户来说,这意味着 Zig 在常见 Linux / BSD / macOS / Windows 目标上的“可用性底线”又往前推了一步;而对于比较边缘的平台,官方也更清晰地区分了“支持”“实验性支持”和“不再支持”。 +### Tier 系统 + +Zig 把对各目标的支持程度划成四档(Tier 1 最高),具体含义如下: + +- **Tier 1**:所有非实验性语言特性都已知正常工作;编译器可以**不依赖 LLVM** 直接为该目标生成机器码 +- **Tier 2**:标准库的跨平台抽象覆盖了该目标;该目标具备调试信息能力,因此在断言失败 / crash 时能给出 stack trace;交叉编译时可获得 libc;CI 在每次推送时都会跑模块测试 +- **Tier 3**:编译器可通过 LLVM 为该目标生成机器码;链接器可生成对象文件、库与可执行文件;该目标在 LLVM 中不被视为实验性 +- **Tier 4**:编译器只能通过 LLVM 为该目标生成汇编源码 + +`Tier 1` 的长期目标是“没有任何被禁用的测试”——这一条也会变成 Zig 1.0 之后版本的硬性要求。 + +### 其他附加目标 + +除了 Tier 1–4 这套划分之外,Zig 对下面这些目标也有不同程度的支持,但 tier 系统本身并不完全适用: + +`aarch64-driverkit`、`aarch64(_be)-freestanding`、`aarch64-uefi`、`alpha-freestanding`、`amdgcn-amdhsa`、`amdgcn-amdpal`、`amdgcn-mesa3d`、`arc(eb)-freestanding`、`arm(eb)-freestanding`、`arm-3ds`、`arm-uefi`、`arm-vita`、`avr-freestanding`、`bpf(eb,el)-freestanding`、`csky-freestanding`、`hexagon-freestanding`、`hppa(64)-freestanding`、`kalimba-freestanding`、`kvx-freestanding`、`lanai-freestanding`、`loongarch(32,64)-freestanding`、`loongarch(32,64)-uefi`、`m68k-freestanding`、`microblaze(el)-freestanding`、`mips(64)(el)-freestanding`、`mipsel-psp`、`msp430-freestanding`、`nvptx(64)-cuda`、`nvptx(64)-nvcl`、`or1k-freestanding`、`powerpc(64)(le)-freestanding`、`powerpc64-ps3`、`propeller-freestanding`、`riscv(32,64)(be)-freestanding`、`riscv(32,64)-uefi`、`s390x-freestanding`、`sh(eb)-freestanding`、`sparc(64)-freestanding`、`spirv(32,64)-opencl`、`spirv(32,64)-opengl`、`spirv(32,64)-vulkan`、`thumb(eb)-freestanding`、`ve-freestanding`、`wasm(32,64)-emscripten`、`wasm(32,64)-freestanding`、`wasm(32,64)-wasi`、`x86(_64)-driverkit`、`x86(_64)-elfiamcu`、`x86(_64)-freestanding`、`x86(_64)-uefi`、`xcore-freestanding`、`xtensa-freestanding`。 + +这些目标常见于嵌入式、unikernel、固件、内核扩展、GPU 计算、UEFI、各类游戏机/掌机、WebAssembly 宿主等场景。 + ## 系统最低版本要求 | 操作系统(Operating System) | 最低版本要求(Minimum Version) | @@ -96,6 +117,42 @@ showVersion: false 如果你升级后遇到以前没见过的 dependency loop,先别急着回退版本。因为 `0.16.0` 的错误报告已经能更清楚地指出环路是怎么形成的,通常只要打断其中一条依赖即可。 +### 一元浮点内建会向下转发结果类型 + +`@sqrt`、`@sin`、`@cos`、`@tan`、`@exp`、`@exp2`、`@log`、`@log2`、`@log10`、`@floor`、`@ceil`、`@trunc`、`@round` 现在都会把外层的结果类型向内转发。意味着以前必须借中间变量才能写的式子(例如 `const x: f64 = @sqrt(@floatFromInt(N));`)现在可以直接成立。这条改动是配合“为做游戏开发改善 ergonomics”的更大计划。 + +### 浮点取整可以直接产出整数 + +`@floor`、`@ceil`、`@round`、`@trunc` 现在不仅会做取整,还能在结果类型是整数时直接给出整数值,省掉一层 `@intFromFloat`。 + +### 局部变量地址不能再被返回 + +返回局部变量地址(`return &local;`)这个新手常见错误现在直接编译报错。 + +这件事的难点其实在于:返回一个无效指针本身是合法的(例如 `fn foo() *i32 { return undefined; }`),非法只发生在解引用之后;甚至 `fn bar() noreturn { unreachable; // equivalent to foo().* }` 也是合法函数。Zig 的解法是“句法层面的较真”:所有“不需要类型检查就能 trivially 降级到 `return undefined` 的表达式”都被禁止,要求你直接写规范的 `return undefined`。`return &local;` 就属于这一类——编译器会给出 `error: returning address of expired local variable 'x'` 这样的清晰诊断,并标出 `var x` 是“declared runtime-known here”。 + +修复方式无非三种:返回值、由调用方传入 buffer、改用堆分配。后续还会有更多类似性质的编译错误被加进来。 + +### Lazy 字段分析 + +`struct` / `union` / `enum` / `opaque` 现在只有在真正需要它的尺寸或字段类型时才会被解析。把类型当作命名空间使用,或者只是构造非解引用指针 `*T`,都不会再触发 `T` 的字段解析。这条改动直接消除了“引用一下 `std.Io.Writer` 就把整个 `std.Io` 的 vtable 都拉进来”这类问题,进而减少了无谓 codegen 和体积膨胀。 + +### 指向 comptime-only 类型的指针不再是 comptime-only + +`comptime_int` 是 comptime-only 类型,但 `*comptime_int` / `[]comptime_int` 现在不是。最直观的例子是函数指针:`*const fn () void` 是运行时类型——你不能在运行时解引用它,但它本身可以在运行时存在。配合反射,这意味着 `[]const std.builtin.Type.StructField` 现在可以直接传给运行时函数,并通过它读出每个字段的 `name`。 + +### `*u8` 与 `*align(1) u8` 不再是同一类型 + +这两个类型现在被视为不同类型(之前 `==` 比较为真)。**两者依然可以互相转换,包括嵌套在指针里**,所以日常用法几乎不需要变。可以理解为类似 `u32` 与 `c_uint` 的差别——技术上不同,行为一致。 + +### Zero-bit tuple 字段不再隐式 `comptime` + +`0.14` 引入的“tuple 中 zero-bit 字段隐式变成 `comptime` 字段”这条规则被回滚。对绝大多数代码无影响——zero-bit 字段的值依然是 comptime-known 的——只有直接读 `StructField.is_comptime` 反射或依赖类型等价性的代码需要调整。 + +### Reworked Byval Syntax Lowering + +编译器前端早期为了减少中间指令尝试用“byval”语义降级表达式,但这个实验带来了数组访问性能问题、显式拷贝下的意外别名、退化场景代码质量极差等一系列 issue。`0.16.0` 改成全程 byref 降级,只在最后一次 load 时才取值,从根上修掉这些问题。 + ## 标准库 ### I/O 作为 Interface @@ -108,6 +165,7 @@ showVersion: false - `Io.Threaded`:基于线程,功能最完整,也是从 `0.15.x` 升级时最接近旧行为的实现 - `Io.Evented`:仍在实验阶段,用来推动接口演进 +- `Io.Uring` / `Io.Kqueue` / `Io.Dispatch`:分别基于 Linux io_uring、kqueue 和 macOS Grand Central Dispatch 的概念验证实现,尚未完整 - `Io.failing`:用于模拟“不支持任何操作”的环境 围绕它,标准库引入了整套新的任务和并发抽象: @@ -117,10 +175,126 @@ showVersion: false - `Batch` - `Select` - `Queue(T)` +- `Clock` / `Duration` / `Timestamp` / `Timeout` 等时间度量类型 - 统一的 cancelation 模型 这不仅是 API 改名,而是 Zig 对“并发 I/O 应该怎样进入语言生态”给出的新答案。文件系统、网络、进程、同步原语、定时器等能力,都围绕这套接口重新组织了。 +#### Future + +Future 是构建在函数之上的任务级抽象。 + +`io.async(fn, args)` 会返回一个 `Future(T)`,其中 `T` 是被调用函数的返回类型。`async` 表达的是“这次调用与其他逻辑相互独立”——也就是 **asynchrony**。因此创建任务是不会失败的,并且能在缺乏并发机制的精简 `Io` 实现上工作(这种实现可以在 `async` 调用里直接同步执行那个函数)。 + +`io.concurrent(fn, args)` 与 `io.async` 类似,但语义上要求“必须并发执行才能保证正确性”。这必然涉及内存分配,所以可能返回 `error.ConcurrencyUnavailable`。 + +`Future(T)` 提供两个方法: + +- `await`:阻塞控制流直到任务完成,并返回函数的返回值 +- `cancel`:和 `await` 相同,但额外请求 `Io` 实现去打断该操作并返回 `error.Canceled`。绝大多数 I/O 操作的错误集合里现在都包含 `error.Canceled` + +为了避免资源泄漏并优雅处理 cancelation,推荐这种写法: + +```zig +var foo_future = io.async(foo, .{args}); +defer if (foo_future.cancel(io)) |resource| resource.deinit() else |_| {} + +var bar_future = io.async(bar, .{args}); +defer if (bar_future.cancel(io)) |resource| resource.deinit() else |_| {} + +const foo_result = try foo_future.await(io); +const bar_result = try bar_future.await(io); +``` + +如果函数没有返回需要释放的资源,那个 `if` 可以简化成 `_ = foo.cancel(io) catch {}`;如果返回 `void`,丢弃也可以省掉。但 `cancel` 这一步是必要的,因为它在出错(包括 `error.Canceled`)时也会释放 async 任务本身占用的资源。 + +#### Group + +当多个任务有相同生命周期时,`Group` 是更合适的选择。它的开销是 O(1):开 N 个任务也只需要常数级的额外空间。 + +下面是一个用 `Group.async` 实现 sleep sort 的例子: + +```zig +const std = @import("std"); +const Io = std.Io; + +test "sleep sort" { + const io = std.testing.io; + + const rng_impl: std.Random.IoSource = .{ .io = io }; + const rng = rng_impl.interface(); + + var array: [10]i32 = undefined; + for (&array) |*elem| elem.* = rng.uintLessThan(u16, 1000); + + var sorted: [10]i32 = undefined; + var index: std.atomic.Value(usize) = .init(0); + + var group: Io.Group = .init; + defer group.cancel(io); + + for (&array) |elem| group.async(io, sleepAppend, .{ io, &sorted, &index, elem }); + + try group.await(io); + + for (sorted[0 .. sorted.len - 1], sorted[1..]) |a, b| { + try std.testing.expect(a <= b); + } +} + +fn sleepAppend(io: Io, result: []i32, i_ptr: *std.atomic.Value(usize), elem: i32) !void { + try io.sleep(.fromMilliseconds(elem), .awake); + result[i_ptr.fetchAdd(1, .monotonic)] = elem; +} +``` + +#### Cancelation + +> 顺便注意拼写:标准命名采用单 `l` 的 `cancelation`。 + +和 for 循环里的“提前 break”是同一类问题——一旦多个任务并发执行,就会遇到“某个任务已经完成或失败,其它任务的结果或副作用对全局已经不再重要,甚至需要回滚”的场景。 + +`Future` / `Group` / `Batch` 都支持发起 cancelation 请求。请求可能被确认,也可能不会。一旦被确认,对应的 I/O 操作会返回 `error.Canceled`。即便是 `Io.Threaded` 也支持这套机制——它通过给线程发信号让阻塞 syscall 返回 `EINTR`,再以此为契机检查 cancelation 请求并决定是否重试。 + +只有“发起这次取消请求”的代码自己才能安全地忽略 `error.Canceled`。其它情况下处理这个错误有三种方式(按常见程度排序): + +1. 把它向上传播 +2. 收到后调用 `io.recancel()`,再选择不传播——这会重新激活 cancelation 请求,下一个检查点会有机会再次确认 +3. 用 `io.swapCancelProtection()` 把它变成 unreachable + +cancel 在语义上等同于 await + 取消请求。也就是说,请求取消之后任务可能依然成功完成,你仍然能拿到返回值——这意味着诸如已分配的资源等副作用必须被正确处理: + +```zig +const std = @import("std"); +const Io = std.Io; + +test "trivial cancel demo" { + const io = std.testing.io; + + var file_task = io.async(Io.Dir.openFile, .{ .cwd(), io, "hello.txt", .{} }); + defer if (file_task.cancel(io)) |file| file.close(io) else |_| {}; +} +``` + +由于 `await` 和 `cancel` 都是幂等的,最常见的写法是“创建任务后立刻 `defer` 取消”,这样无论函数从哪里返回都能保证并发任务被回收。 + +通常 Zig 程序员不需要主动写代码支持 cancelation,因为 `error.Canceled` 已经被烘焙进所有可取消 I/O 操作的错误集合。但如果有长时间运行的 CPU 密集任务需要响应取消,可以显式调用 `io.checkCancel` 添加额外的 cancelation point。 + +#### Batch + +`Batch` 是更底层的并发原语,它在 `Operation` 这一层提供并发能力。优点是高效且可移植,缺点是抽象起来更难——尤其当你需要在操作之间穿插一些自定义逻辑时。 + +未来大部分文件系统和网络功能预计都会迁到基于 `Operation` 的实现上,进而都能配合 `Batch` 使用,并通过 `operateTimeout` 给**任意** I/O 操作加超时。当前能用 Batch 跑的 Operation 列表是: + +- `FileReadStreaming` +- `FileWriteStreaming` +- `DeviceIoControl` +- `NetReceive` + +而 Future 是同样思想在“函数”层面的版本——更灵活、更顺手,但在使用 `concurrent` 时需要分配任务内存并可能返回 `error.ConcurrencyUnavailable`,使用 `async` 时也可能引入意外的阻塞操作。 + +简单说:写最优、可复用的代码时,如果你只是想同时做几件事,`Batch` 更合适;如果绕开它会让你重新发明 future,那就用 Future。也可以先用 Future 写出来,之后再把热点路径改造成 Batch 来减少任务开销。 + ### `Juicy Main` 为了配合新的 `std.Io`,`main` 也获得了一个很实用的新入口:`std.process.Init`。 @@ -181,13 +355,96 @@ pub fn main(init: std.process.Init) !void { 这些调整单看都不算大新闻,但合在一起,就是一次很典型的 Zig 式“去历史包袱”整理。 +### 调试信息与栈追踪重做 + +`0.16.0` 重做了一批和调试信息相关的标准库 API,特别是栈追踪(stack trace)。这件事的真正动机是:**在没有帧指针(例如 libc 用 `-fomit-frame-pointer` 编译)的前提下,依然能做到“快又安全”的栈展开**——既不需要为每个栈帧检查地址是否越界、又能避开过去那些“崩溃时去做栈展开反而再次崩溃”的边角情况。 + +这其实是个比想象中复杂得多的问题,因为它真正的解法是 unwind information,而不同目标对 unwind 信息的编码方式各不相同。Zig 的标准库以前虽然也支持过 unwind 信息,但实现 buggy 又不完整,性能也常常拖后腿。`0.16.0` 之后,标准库默认会优先使用基于 unwind 的“安全”栈展开;和原来基于帧指针的展开相比,性能开销在大多数场景下是可以接受的。 + +这条改动同样是“为做游戏开发改善 Zig 体验”的一部分。 + +### 跨进程进度上报支持 Windows + +`std.Progress` 现在可以在 Windows 上跨进程上报进度,子进程的进度会自动反映到父进程的进度树里。同时,最大节点名称长度也从 40 提升到 120。 + +这件事看起来很小,但对“构建系统跑大量子工具”的工作流来说体感差异很明显:以前 Windows 下子工具只能各自打印日志,现在可以像 Linux / macOS 一样汇聚到统一的进度面板。 + +### Windows 网络不再依赖 `ws2_32.dll` + +`0.16.0` 把 Windows 上所有网络 API 直接迁到了 AFD(Ancillary Function Driver),不再走 `ws2_32.dll`。 + +这个改动一次性带来了几个实际收益: + +- 修掉了一批历史 bug +- 让 cancelation 和 Batch 在网络操作上正确工作 +- 避开了 `ws2_32.dll` 内部的性能陷阱——例如它会为每个 socket handle 维护一个完全多余的 hash table,需要分配和同步,而不是简单地在 `accept` 时把 socket mode 和 protocol 一起传下去 + +简单一句话:Windows 上网络 API 现在更稳、更快,也更适配 `std.Io` 的并发模型。 + +### 完成向 NtDll 的迁移 + +`0.16.0` 之后,Windows 上**几乎所有标准库功能**都直接基于最低层级、稳定的 syscall 接口实现。剩下还会显式调用 Windows DLL 的 extern 函数只剩这些: + +- `kernel32.CreateProcessW` +- `crypt32` 一组:`CertOpenStore` / `CertCloseStore` / `CertEnumCertificatesInStore` / `CertFreeCertificateContext` / `CertAddEncodedCertificateToStore` / `CertOpenSystemStoreW` / `CertGetCertificateChain` / `CertFreeCertificateChain` / `CertVerifyCertificateChainPolicy` + +这套迁移直接修掉了一批 Windows 上的 bug、性能问题和缺失功能,让 Zig 程序在 Windows 上比许多其它语言更精简、更可靠。Batch 与 Cancelation 这两套在 Windows 上能用并且实现高效,就是这次迁移直接带来的结果。 + +如果你想兼容 XP 这类更老的 Windows 版本,或者就是更倾向于走 kernel32 这种较高层的 DLL,官方建议作为社区方案做一份不依赖 NtDll 的第三方 `Io` 实现。`CreateProcessW` 与 `crypt32` 这一组短期内不会再继续迁移。 + +### Deflate 压缩器与解压缩简化 + +`std.compress.flate` 这一轮**新增了从零实现的 deflate 压缩器**:以 writer 缓冲区作为历史窗口、用链式哈希表寻找匹配,token 累积到阈值后整块输出。除此之外,还提供了两种额外的 writer: + +- `Raw`:完全只输出 store block(即未压缩字节),借助数据向量高效发送 block header 与数据 +- `Huffman`:只做 Huffman 压缩,不做匹配 + +这两者因为不需要保留历史,可以更直接地利用新的 writer 语义。 + +`token` 中的字面量与距离编码参数也被重做:参数现在是数学方法推导出来的,更昂贵的那部分依然走查表(`ReleaseSmall` 例外)。 + +解压侧的 bit 读取也大幅简化,充分利用了底层 reader 可以 peek 的能力,并修掉了若干和 limit 处理相关的 bug。 + +#### 与 zlib 的对比 + +性能与压缩比层面,简单概括是: + +- **压缩比**:默认压缩级别下 zlib 比 Zig 的实现高 1.00%,最高压缩级别下高 0.77%——zlib 选取的匹配略有不同,但匹配字节总数其实更少,未来还可以继续打磨向 zlib 看齐 +- **默认级别压缩性能**:Zig 的 std-deflate 比 zlib 大约**快 9.7%**,CPU 周期少 9.8%,cache miss 与分支误预测都明显更少;instruction 数因为算法选择不同会高约 18.9%,但被更友好的访存模式抵消 +- **最高级别压缩性能**:和 zlib 持平(差异在 1% 以内) +- **解压性能(vs 上一版)**:新解压实现比 0.15.x 大约**快 9.5%**,CPU 周期与指令数都减少约 10%,分支误预测下降约 18% + +换句话说,Zig `0.16.0` 自带的 deflate 在常见场景下已经能在性能上和 zlib 打平甚至略胜,压缩比仍然是后续的优化方向。 + +### `std.crypto` 新增 AES-SIV / AES-GCM-SIV + +过去 Zig 标准库一直缺少“可抗 nonce 重用”的 AEAD 方案;`0.16.0` 把这一缺口补上了: + +- **AES-SIV**:在密钥包装(key wrapping)这类场景里特别有用 +- **AES-GCM-SIV**:在嵌入式目标上尤其合适 + +这两个原语都是“nonce reuse-resistant”AEAD 的事实标准选择。 + +### `std.crypto` 新增 Ascon-AEAD / Ascon-Hash / Ascon-CHash + +Ascon 是 NIST 为轻量级密码学标准化的一系列构造。Zig 标准库以前已经提供了 Ascon 置换本身,但建立在置换之上的高层构造一直被刻意推迟,等待 NIST 发布最终规范。 + +NIST SP 800-232 已经正式发布之后,Zig `0.16.0` 把这一组高层构造一次性补齐: + +- `Ascon-AEAD`:AEAD 加密 +- `Ascon-Hash`:定长哈希 +- `Ascon-CHash`:可定制化的哈希 + +对嵌入式 / IoT 场景这是一项久违的能力补齐。 + ### Windows 标准库实现继续下沉 -Windows 也是 `0.16.0` 里非常有意思的一条线: +Windows 也是 `0.16.0` 里非常有意思的一条线,前面几节已经覆盖了核心内容: - 网络 API 不再依赖 `ws2_32.dll`,而是直接基于 AFD 实现 -- 标准库继续向 NtDll 收敛 +- 标准库基本完成向 NtDll 收敛,仅剩 `CreateProcessW` 与一组 `crypt32` 函数 - `std.Progress` 现在也支持 Windows 下的跨进程进度上报 +- inline caller 现在会从 PDB 调试信息里被解析;如果 debug info 模糊,会把所有候选 caller 都打印出来;error return trace 也会在所有平台上包含 inline caller 这些工作虽然对多数用户不可见,但会真实影响程序的健壮性、性能,以及 cancelation / batch 模型在 Windows 上的完整度。 @@ -243,13 +500,48 @@ Windows 也是 `0.16.0` 里非常有意思的一条线: ### `translate-c` 改用 Aro -编译器内部的 `translate-c` 现在基于 Aro / translate-c,而不是 `libclang`。这使 Zig 离“摆脱对 LLVM 的库级依赖”又近了一步。 +编译器内部的 `translate-c` 现在基于 Aro / translate-c,而不是 `libclang`。这一改动直接从编译器源码树里删掉了 5,940 行 C++(剩余 3,763 行),也让 Zig 离“把对 LLVM 的库级依赖换成对 Clang 的进程级依赖”又近了一步。 + +对普通用户来说,这件事大体上是非破坏性的——但也确实是“一个 C 编译器替换成另一个”,所以如果你升级后发现某个 C 头文件翻译结果不一致,请按 bug 反馈。 + +实现是“懒加载”的:第一次遇到 `@cImport` 时再从源码构建。 + +### LLVM 后端 -对普通用户来说,这更多体现为长期方向上的信号:Zig 仍在持续拆除自己对 LLVM 的深绑定。 +LLVM 后端这一轮的进展有几条: + +- **实验性支持增量编译**——这并不会加速 “LLVM Emit Object” 这一步(LLVM 自己负责的部分我们做不了什么),但加速了 Zig 编译器侧生成 LLVM bitcode 的过程,因此当你的代码本身就有编译错误时,你能在 LLVM 后端下也获得近乎瞬时的反馈 +- LLVM bitcode 体积下降 3-7% +- 在某些情况下编译速度略提升约 3% +- 修掉了零字节 payload union 的调试信息 +- 调试信息现在对所有类型都包含正确的名字 +- error set 类型现在以 enum 方式 lower,使得 error 名字在运行时可见 + +LLVM 后端目前通过了 2004 / 2010(100%)行为测试。Matthew 还实验过把 tagged union 和 error union 用 DWARF 的 variant 类型表达,让调试器只显示“当前激活的字段”——但 LLDB 对 variant 类型的支持只有在语言被标成 Rust 时才启用,因此暂未落地。这条路径未来下游若改善还可能再走。 + +后续还会继续推进 LLVM 后端的并行化:让多个线程同时为不同函数生成 LLVM IR,再由一个 “linker” 线程合并。 + +### 重做 byval 语法降级 + +编译器前端早期为了减少中间指令尝试用“byval”语义降级表达式,但这个实验带来了三类典型问题:数组访问性能问题、显式拷贝下的意外别名、退化场景代码质量极差。 + +`0.16.0` 改成全程 byref 降级,只在最后一次 load 时才取值,从根上修掉这些问题。 ### 类型解析重构 -前面在“语言变动”里提过,`0.16.0` 大幅重做了类型解析。这件事对编译器本身还有两个非常重要的连锁收益: +前面在“语言变动”里提过,`0.16.0` 大幅重做了类型解析。这件事的真正动机有两个:简化 Zig 语言规范的撰写,以及修掉一大批和增量编译相关的编译器 bug。 + +新规则总体上比旧规则**更宽松**:以前能 work 的代码大多数仍然能 work,一些以前会因为 dependency loop 报错的代码现在反而能正常编译。但它并不是“严格更宽松”——例如下面这种结构体在自身对齐查询里依赖 `@This()` 的写法,从 `0.16.0` 起会被识别为依赖环并直接报错: + +```zig +const S = struct { + foo: [*]align(@alignOf(@This())) u8, +}; +``` + +不过这次配套的错误信息也清晰了许多。例如某条长度为 3 的依赖环——“S 默认字段值用了 default_val,default_val 用了 other_val,other_val 又通过 `@typeInfo(S)` 回到 S”——错误输出会把这三跳依次列出来,并提示“破坏其中任何一条都能解开环”。 + +这件事对编译器本身还有两个非常重要的连锁收益: - 依赖环报错更可解释 - 增量编译和普通编译之间的一致性更强 @@ -258,12 +550,13 @@ Windows 也是 `0.16.0` 里非常有意思的一条线: ### 增量编译继续前进 -`0.16.0` 的增量编译已经比 `0.15.x` 实用得多: +增量编译让 Zig 编译器只重新编译自上次构建以来真正改动的代码——小改动可以从“秒 / 分钟”降到“毫秒”。`0.16.0` 这一轮里它的进步非常具体: -- 大多数场景下减少了“过度重编译” -- LLVM 后端也开始支持增量编译 -- ELF 目标在 `-fincremental` 下会默认启用新的 ELF linker -- 稳定性明显提升,虽然依然不是默认开启 +- **避免“过度分析”**——大部分场景下,编译器不再因为某个改动牵连出本不需要重建的代码。在 Zig 编译器自己身上做实验:以前会几乎重新编译整个编译器的改动,现在能在毫秒级完成。这里的关键,是“类型解析重做”让编译器内部依赖图变成了无环图(dependency loop 除外) +- 增量编译和非增量编译之间,不再因为对方触发 dependency loop 而表现不一致。这是上一版里最大的不一致来源 +- 在 ELF 目标上,自托管后端 + `-fincremental` 时**默认启用新的 ELF linker**——更快、对增量编译的支持也更稳 +- 整体稳定性明显提升,崩溃和误编译比上一版少得多 +- LLVM 后端现在也支持增量编译 官方现在明确鼓励大家实际使用: @@ -271,15 +564,34 @@ Windows 也是 `0.16.0` 里非常有意思的一条线: zig build -fincremental --watch ``` -当然,它仍然有已知 bug,甚至可能包含误编译;所以这项功能在 `0.16.0` 里依然不是默认值。 +哪怕只是“近乎瞬时拿到编译错误”,多数用户也会被它实际的提速程度震惊到。当然,它仍然有已知 bug、甚至可能包含误编译;所以 `0.16.0` 里增量编译依然不是默认开启。 + +### x86 后端 + +`0.16.0` 里 x86 自托管后端: + +- 修复 11 个 bug +- 常量 `memcpy` 的代码生成更优 + +和 LLVM 后端相比,x86 后端通过的行为测试更多、编译速度显著更快、调试信息更优,机器码质量稍逊;它仍然是 Debug 模式下的默认后端。 + +### aarch64 后端 + +仍处于开发中。这个版本周期内,由于 `std.Io` 带来的标准库 churn,aarch64 后端的进度暂时放慢;当前在跑行为测试时会崩溃。等标准库这边的 churn 结束之后,进度会重新拾起。 + +### WebAssembly 后端 + +目前通过了 1813 / 1970(约 92%)项行为测试(与 LLVM 后端做对比)。 + +### 不再依赖 LLVM 生成 .def 导入库 -### 后端进展 +`0.16.0` 把“从 .def 文件生成导入库”这件事从 LLVM 那边切了出来——具体面向的是 Zig 自带的 MinGW-w64 .def 文件。新实现大体参考 LLVM 的 `COFFModuleDefinition.cpp` 与 `COFFImportFile.cpp`。 -这一轮里: +这条同样是“把对 LLVM 的库级依赖转向对 Clang 的进程级依赖”这条长期路线的一环。 -- x86 自托管后端修了 11 个 bug,仍然是 Debug 模式下的默认后端 -- aarch64 后端因为 `std.Io` 带来的标准库 churn 暂时放慢了节奏 -- Zig 的 WebAssembly 后端目前通过了 1813 / 1970(约 92%)项行为测试 +### for 循环安全检查的代码生成改进 + +针对 slice 的 for 循环,安全检查相关的代码生成减少了大约 **30%**。这意味着典型的循环代码在 Debug 构建里不仅仍然带越界检查,体积也明显小于上一版。 ## 链接器(Linker) @@ -287,11 +599,15 @@ zig build -fincremental --watch `0.16.0` 的新 ELF linker 可以通过 `-fnew-linker` 显式启用,或者在 build 脚本里设置 `exe.use_new_linker = true`。更重要的是:**在 `-fincremental` 且目标是 ELF 时,它现在会默认启用。** -官方给出的一个数据点非常直观:对 Zig 编译器本体做单行改动时,旧 linker 需要大约 `194ms`,而新 linker 只需要 `65ms`,几乎接近“完全跳过链接”的速度。 +官方给出的一组数据点非常直观——对 Zig 编译器本体先做一次完整构建,然后做一次单行改动,再做第二次单行改动: + +- 旧 linker:`14s`、`194ms`、`191ms` +- 新 linker:`14s`、`65ms`、`64ms`(快约 66%) +- 完全跳过链接:`14s`、`62ms`、`62ms`(快约 68%) -这也意味着过去那种专门暴露 `-Dno-bin`、只求快速拿到编译错误的工作流,收益已经没有以前那么明显了。 +这也意味着过去那种专门暴露 `-Dno-bin`、只求快速拿到编译错误的工作流,**收益已经几乎可以忽略**——你大可以让 codegen 和链接一直开着,构建结束顺手拿到一个可执行文件。 -不过要注意,新 linker 目前还没完全补齐旧 linker / LLD 的能力,例如生成物还缺少 DWARF 信息。所以它已经够快、够值得试,但还没有到“所有场景都能无脑切换”的程度。 +不过要注意,新 linker 目前**还没补齐旧 linker / LLD 的能力**,例如生成物还缺少 DWARF 信息。所以它已经够快、够值得试,但还没有到“所有场景都能无脑切换”的程度。当新 linker 功能完全对齐之后,旧 linker 会被删掉,LLD 也会从依赖里移除。 ## Fuzzer(模糊测试器) @@ -299,54 +615,114 @@ zig build -fincremental --watch Fuzz 测试接口是 `0.16.0` 里另一个会直接影响用户代码的 breaking change。过去 fuzz test 习惯接收 `[]const u8` 输入;现在统一改成 `*std.testing.Smith`,由它来生成结构化值。 -这套接口不仅更适合做复杂输入生成,还支持权重、范围、哈希相关性等能力,明显比过去的“原始字节切片”模式更强。 +`Smith` 提供的基础方法包括: + +- `value`:生成任意类型的值 +- `eos`:生成 end-of-stream 标记,且保证“最终一定会返回 `true`” +- `bytes`:填充字节数组 +- `slice`:填充缓冲区的一部分并给出长度 + +每个方法都有支持权重的对应版本,权重通过 `[]const Smith.Weight` 表达,可以用来: + +- 让“有意思”的值出现得更频繁 +- 减少不必要的工作量 +- 限制可选值的范围 + +值得注意的是,权重只能用于能放进 64 位的类型;空权重切片意味着所有值的权重为零,因此都不会被选中。除此之外还提供: + +- `baselineWeights`:为某个类型构造覆盖所有可能值的权重集 +- `boolWeighted` / `eosSimpleWeighted`:方便地为 `true` / `false` 配权重 +- `valueRangeAtMost` / `valueRangeLessThan`:只生成某个范围内的值 + +每个方法还有一个接收 hash 参数的对应版本——同 hash 的值更倾向于一起被变异。常规方法本身已经基于 callee 返回地址生成 hash,因此你通常不需要手动调用这一组,主要是 inline 的场景下才会用到。 ### 多进程、多核与 crash dump 除了接口变化之外,fuzzer 本身也更强了: -- 现在可以利用多核,受 `-j` 控制 -- 多个 fuzz test 会自动轮换并优先运行更“有产出”的测试 -- 崩溃输入会自动落盘,便于复现 +- **多进程 / 多核**:现在可以利用多核,受 `-j` 构建选项控制;受限模糊(limited fuzzing)仍然只用一个核 +- **无限模式**:当提供多个 fuzz test 时,fuzzer 会在它们之间切换并**优先运行更“有产出”的测试**;随着时间推移,已经被反复探索过的测试会被分得越来越少的预算 +- **崩溃输入会自动落盘**:crash 信息中会指出落盘文件路径,便于配合 `std.testing.FuzzInputOptions.corpus` 与 `@embedFile` 复现 + +### 配套的 AST Smith 已经替 zig fmt 找到 20 个 bug + +新的 Smith 接口本身已经被用来构建一个 “AST Smith”——专门生成随机但合法的 AST。把它丢给 `zig fmt`(再加上一些更早期的简单随机源码测试),一共发现并修复了 **20 个独立 bug**,其中一部分是新发现的。 + +它还顺手修了几个“PEG 与 parser 不一致”的问题,例如以前 tuple 不能包含以 `extern` 或 `inline` 起头的类型——`const T = struct { u64, extern struct { a: u64 }, u32 }` 在以前会直接报错。 ## Bug 修复 -本轮发布周期内,Zig 一共关闭了 345 个 bug 报告。 +本轮发布周期内,Zig 一共关闭了 **345 个 bug 报告**。许多 bug 是在本周期内被引入又被修复的;为了简洁,绝大多数 bug 修复并未单独列出。 + +### 这个版本仍然包含已知 bug + +官方明确写在 release notes 里—— -不过官方也直说了:**这个版本仍然包含已知 bug、误编译和回归问题。** 对于稍微复杂一点的项目,使用 `0.16.x` 仍然意味着要准备好参与 issue 反馈、最小复现和版本试验。 +> Zig 仍然存在已知 bug、误编译(miscompilation)和回归(regression)。 -这并不意外。因为 `0.16.0` 本质上是一个“大迁移版本”,它把很多还在演进中的长期工程方向一次性推到了用户面前。 +对稍微复杂一点的项目,使用 `0.16.x` 仍然意味着要准备好参与开发流程:提交最小复现、跟踪 issue、必要时切版本验证。 + +这并不意外。因为 `0.16.0` 本质上是一个“大迁移版本”,它把很多还在演进中的长期工程方向一次性推到了用户面前。Zig 1.0 之后,Tier 1 支持会获得一项额外要求:bug policy。 ## 工具链(Toolchain) ### LLVM 21 -`0.16.0` 升级到了 LLVM `21.1.0`,覆盖了 Clang、libc++、libc++abi、libunwind、libtsan 等组件。 +`0.16.0` 升级到了 LLVM `21.1.0`,覆盖了 Clang(即 `zig cc`)、libc++、libc++abi、libunwind、libtsan 等组件。 + +#### 为绕过回归,loop vectorization 被全局关闭 + +这里有一个非常值得注意的 caveat:LLVM 21 上游存在一个严重回归,会在常见配置下**误编译 Zig 编译器自身**。试图通过禁用某些 CPU 特性来绕过太脆弱,所以 Zig 在 `0.16.x` 中**完全禁用了 loop vectorization**。这会让某些代码生成结果比理想情况更保守,但它仍然比“在常见配置下误编译 Zig 编译器自身”要好得多。 + +这个 bug 已经向上游报告并修复,但写文档时,修复尚未被 cherry-pick 到 LLVM 22.x 分支。因此官方预计这个性能回退不止会影响 `0.16.x`,还会延续到 `0.17.x`,大概率要等到 `0.18.x` 才会彻底解决。 + +### musl 1.2.5 + +`0.16.0` 分发 musl 1.2.5 + 回移的安全修复。上游已经标了 1.2.6,未来某个 Zig 版本会跟进。 + +由于很多函数被迁到了 `zig libc`,相比上一版**少分发了 331 个 musl C 源文件**(剩 1,206 个)。这意味着如果你遇到 musl libc 相关 bug,应当先报到 Zig 的 issue tracker 而不是 musl。 + +另外,`0.16.0` 不受 `CVE-2026-40200` 影响——musl 的 `qsort` / `qsort_r` 已经不再被使用。 + +### glibc 2.43 + +交叉编译时现在可以选择 glibc 2.43。 + +### Linux 6.19 headers + +随 Zig 分发的 Linux 内核头文件升级到 6.19。 + +### macOS 26.4 headers + +随 Zig 分发的 macOS 系统头文件升级到 26.4。 + +### MinGW-w64 + +`0.16.0` 继续分发 MinGW-w64 commit `38c8142f660b6ba11e7c408f2de1e9f8bfaf839e`。但很多函数已经迁到 `zig libc`:本版相比上一版**少分发 99 个 MinGW-w64 C 源文件**(剩 398 个)。同样地,相关 bug 应当先报到 Zig 这边而不是 MinGW-w64。 + +### FreeBSD 15.0 libc + +交叉编译时现在可以选择 FreeBSD libc 15.0。 -不过这里有一个非常值得注意的 caveat:为了规避 LLVM 上游的严重回归,Zig 在 `0.16.x` 中**完全禁用了 loop vectorization**。这会让某些代码生成结果比理想情况更保守,但它仍然比“在常见配置下误编译 Zig 编译器自身”要好得多。 +### WASI libc -官方预计这个性能回退不止会影响 `0.16.x`,甚至还会延续到 `0.17.x`,大概率要等到 `0.18.x` 才会彻底解决。 +`0.16.0` 升级到 WASI libc commit `c89896107d7b57aef69dcadede47409ee4f702ee`。 -### libc 与系统头文件更新 +虽然很多函数已经迁到 `zig libc`,但因为新版 WASI libc 添加了 pthread shim,且 WASI libc 大部分源文件其实和 musl 共享,**WASI libc 自身的源文件数反而从 196 增加到 228**。 -这一轮工具链同时带来了: +### zig libc -- `musl 1.2.5`(附带安全修复回移) -- `glibc 2.43` -- Linux `6.19` headers -- macOS `26.4` headers -- FreeBSD `15.0` libc -- 交叉编译时支持动态链接的 OpenBSD libc +`zig libc` 这一轮继续吞并原来来自 musl、MinGW-w64、WASI libc 的一部分 C 实现。Zig 分发的 C 源文件总数从 `2,270` 降到了 `1,873`,减少了约 `17%`。 -### `zig libc` 继续扩张 +这里尤其值得一提的是:**很多数学函数,以及 `malloc` 系列函数**,现在都已经进入 `zig libc` 的实现范围。这条进展受益于 Szabolcs Nagy 提供的 [libc-test](https://wiki.musl-libc.org/libc-test.html)。 -`zig libc` 继续吞并原来来自 musl、MinGW-w64、WASI libc 的一部分 C 实现。`0.16.0` 中,随 Zig 分发的 C 源文件总数从 `2270` 降到了 `1873`,减少了约 `17%`。 +### zig cc -这里尤其值得一提的是:很多数学函数,以及 `malloc` 相关函数,现在都已经进入 `zig libc` 的实现范围。 +`zig cc` / `zig c++` 现在基于 Clang `21.1.8`。这意味着 Zig 在“C / C++ 工具链外壳”这条线上,也继续和整体 LLVM 版本一同推进。本周期内一共修复了 **9 个 zig cc 相关 bug**。 -### `zig cc` +### 交叉编译时支持动态链接的 OpenBSD libc -`zig cc` / `zig c++` 现在基于 Clang `21.1.8`。这意味着 Zig 在“C / C++ 工具链外壳”这条线上,也继续和整体 LLVM 版本一同推进。 +Zig 现在允许交叉编译到 OpenBSD 7.8+:和 glibc 类似,会提供动态 libc 的 stub library,并附带全部 libc 头文件以及大多数系统头文件。 ## 路线图(Roadmap) @@ -354,10 +730,10 @@ Fuzz 测试接口是 `0.16.0` 里另一个会直接影响用户代码的 breakin 在这之后,更长期的大方向仍然是: -- 继续完成并稳定语言本身 -- 做完 aarch64 后端,并让它成为 Debug 模式默认后端 -- 继续增强链接器,减少对 LLD 的依赖,并服务增量编译 -- 继续增强内置 fuzzer -- 继续把对 LLVM 的依赖,从“链接库”转向“调用 Clang 进程” +- **继续完成并稳定语言本身** +- **做完 aarch64 后端**,并让它成为 Debug 模式默认后端 +- **继续增强链接器**,减少对 LLD 的依赖,并服务增量编译 +- **继续增强内置 fuzzer**,让它和 AFL 等业界最先进的 fuzzer 处于同一水平线 +- **继续把对 LLVM 的依赖,从“链接库”转向“调用 Clang 进程”** 如果说 `0.15.x` 还是“这些方向马上就要影响到你了”,那 `0.16.0` 就是“它们已经开始真正影响你的日常开发了”。 diff --git a/course/update/upgrade-0.16.0.md b/course/update/upgrade-0.16.0.md index f69bdd5a..6fbd0c7f 100644 --- a/course/update/upgrade-0.16.0.md +++ b/course/update/upgrade-0.16.0.md @@ -11,16 +11,41 @@ showVersion: false ### `switch` 能力继续增强 -`0.16.0` 继续补齐了 `switch` 的一些语义细节。最直观的一点是:`packed struct` 和 `packed union` 现在也可以直接作为 prong item 使用,并且比较规则基于它们的 backing integer。 +`packed struct` 和 `packed union` 现在可以直接作为 switch 的 prong item,并且**比较规则完全基于其 backing integer**——和 packed 类型自身的相等比较保持一致: -此外,下面这些行为也得到增强或修复: +```zig +const U = packed union(u2) { + a: i2, + b: u2, +}; + +const u: U = .{ .a = -1 }; +switch (u) { + .{ .b = 3 } => {}, + else => unreachable, +} +``` + +这一版本里 switch 还新增了以下能力: -- 需要结果类型的表达式(例如 `@enumFromInt`)可以直接写进 `switch` prong -- 联合类型的 tag capture 不再只限于 `inline` prong -- 对 `void` 做 `switch` 时,不再一律强制要求 `else` -- 一些错误集合和 one-possible-value 类型上的历史问题被修复了 +- decl literals 以及其他需要结果类型的表达式(例如 `@enumFromInt`)现在可以直接作为 switch prong item +- 联合类型的 tag capture 不再只限于 `inline` prong——所有 prong 都可以使用 +- 如果 prong body 是 `=> comptime unreachable`,prong item 中允许出现“当前 error 集合里并不存在的错误” +- **prong capture 不再允许被全部丢弃**——这是一条破坏性改动,如果你之前在 prong 里把所有 capture 都写成了 `_`,需要至少保留一个真实变量名 -这部分通常不需要你主动迁移,但如果你有比较复杂的 `switch` 写法,可以顺便回头看一下是否能更简洁。 +bug 修复方面: + +- 大量 “one-possible-value” 类型上的 switch 相关 bug 被修复 +- 在 error 上做 switch 时,关于 unreachable `else` prong 的规则现在适用于**所有**对 error 的 switch,而不仅是 `switch_block_err_union`,且基于 AST 正确判断 +- 对 `void` 做 switch 时,不再无条件要求 `else` prong +- lazy values 在与 prong item 比较前会被正确求值 +- 不同形式的 switch 语句(带 / 不带 label)求值顺序现在保持一致 + +绝大多数代码不需要主动迁移,但 prong capture 全部丢弃的这条规则会让一小部分写法直接编译报错。 + +### `packed union` 的相等比较 + +`packed union` 现在支持直接相等比较(基于 backing integer),不再需要绕到 backing integer 显式 `@bitCast` 再比。这与上面 switch 中“按 backing integer 比较”的语义是一致的。 ### `@cImport` 开始迁移到构建系统 @@ -56,6 +81,8 @@ const translate_c = b.addTranslateC(.{ .target = target, .optimize = optimize, }); +translate_c.linkSystemLibrary("glfw", .{}); +translate_c.linkSystemLibrary("epoxy", .{}); const exe = b.addExecutable(.{ .name = "example", @@ -77,6 +104,8 @@ const exe = b.addExecutable(.{ const c = @import("c"); ``` +这样翻译出来的 C 代码与以前用 `@cImport` 的结果是一致的;如果需要更细的翻译参数控制,可以把官方 `translate-c` 包作为显式依赖加入。 + 如果你升级到 `0.16.0` 后发现同一份 C 头文件翻译结果和以前不一致,也不用急着怀疑自己。因为 `translate-c` 的底层实现已经从 `libclang` 切换到了 Aro,这类差异更应该视为 bug 并反馈给 Zig。 ### `@Type` 被拆分为独立的类型构造内建 @@ -126,6 +155,190 @@ std.meta.Tuple(&.{ u32, [2]f64 }) @Tuple(&.{ u32, [2]f64 }) ``` +为了简化语言,**不再支持反射出带 `comptime` 字段的 tuple 类型**。 + +`@Pointer` 等价于 `@Type(.{ .pointer = ... })`,但配合新的 `std.builtin.Type.Pointer.Attributes` 类型——后者借助 struct 字段默认值,使得用法更接近字面 pointer 类型语法: + +```zig +@Type(.{ .pointer = .{ + .size = .one, + .is_const = true, + .is_volatile = false, + .alignment = @alignOf(u32), + .address_space = .generic, + .child = u32, + .is_allowzero = false, + .sentinel_ptr = null, +} }) +``` + +⬇️ + +```zig +@Pointer(.one, .{ .@"const" = true }, u32, null) +``` + +```zig +@Type(.{ .pointer = .{ + .size = .many, + .is_const = false, + .is_volatile = false, + .alignment = 1, + .address_space = .generic, + .child = u64, + .is_allowzero = false, + .sentinel_ptr = &@as(u64, 0), +} }) +``` + +⬇️ + +```zig +@Pointer(.many, .{ .@"align" = 1 }, u64, 0) +``` + +`@Fn` 等价于 `@Type(.{ .@"fn" = ... })`。参数分两段:第一段是所有参数类型,第二段是“参数属性”(目前只有 `noalias` 这个 flag): + +```zig +@Type(.{ .@"fn" = .{ + .calling_convention = .c, + .is_generic = false, + .is_var_args = true, + .return_type = u32, + .params = &.{.{ + .is_generic = false, + .is_noalias = false, + .type = f64, + }, .{ + .is_generic = false, + .is_noalias = true, + .type = *const anyopaque, + }}, +} }) +``` + +⬇️ + +```zig +@Fn( + &.{ f64, *const anyopaque }, + &.{ .{}, .{ .@"noalias" = true } }, + u32, + .{ .@"callconv" = .c, .varargs = true }, +) +``` + +这是几个新 builtin 中用 “struct of arrays” 风格接收参数的代表。这种风格的好处是“给所有元素一个统一默认值”非常容易——比如想给所有参数用默认属性 `.{}`,用 `&@splat(.{})` 即可: + +```zig +@Fn(param_types, &@splat(.{}), ReturnType, .{ .@"callconv" = .c }) +``` + +`@Struct` 同样采用 “struct of arrays”:字段名、字段类型、字段属性各自传一组数组,属性里包含 alignment、`comptime` 标志、字段默认值: + +```zig +@Type(.{ .@"struct" = .{ + .layout = .@"extern", + .fields = &.{.{ + .name = "foo", + .type = [2]f64, + .default_value_ptr = null, + .is_comptime = false, + .alignment = 1, + }, .{ + .name = "bar", + .type = u32, + .default_value_ptr = &@as(u32, 123), + .is_comptime = true, + .alignment = @alignOf(u32), + }}, + .decls = &.{}, + .is_tuple = false, +} }) +``` + +⬇️ + +```zig +@Struct( + .@"extern", + null, + &.{ "foo", "bar" }, + &.{ [2]f64, u32 }, + &.{ + .{ .@"align" = 1 }, + .{ .@"comptime" = true, .default_value_ptr = &@as(u32, 123) }, + }, +) +``` + +同样可以用 `&@splat(.{})` 表达“所有字段都用默认属性”,甚至连 field types 都可以——例如要构造一个所有字段都是 `FieldType`,并且字段名跟 `MyEnum` 的枚举名一致的 struct: + +```zig +const MyStruct = @Struct(.auto, null, std.meta.fieldNames(MyEnum), &@splat(FieldType), &@splat(.{})); +``` + +`@Union` 用法与 `@Struct` 类似: + +```zig +@Type(.{ .@"union" = .{ + .layout = .auto, + .tag_type = MyEnum, + .fields = &.{.{ + .name = "foo", + .type = i64, + .alignment = @alignOf(i64), + }, .{ + .name = "bar", + .type = f64, + .alignment = @alignOf(f64), + }}, + .decls = &.{}, +} }) +``` + +⬇️ + +```zig +@Union( + .auto, + MyEnum, + &.{ "foo", "bar" }, + &.{ i64, f64 }, + &@splat(.{}), +) +``` + +`@Enum` 和 `@Struct` 风格相近,但接收的是字段 **tag 值** 数组而不是字段类型数组: + +```zig +@Type(.{ .@"enum" = .{ + .tag_type = u32, + .fields = &.{.{ + .name = "foo", + .value = 0, + }, .{ + .name = "bar", + .value = 1, + }}, + .decls = &.{}, + .is_exhaustive = true, +} }) +``` + +⬇️ + +```zig +@Enum( + u32, + .exhaustive, + &.{ "foo", "bar" }, + &.{ 0, 1 }, +) +``` + +需要注意的是,**这套新 builtin 里没有 `@Float`**——因为运行时浮点类型只有 5 种,在用户代码里实现这件事很轻松;如果非要从位数构造浮点类型,可以用 `std.meta.Float`。 + 如果你的项目大量依赖元编程,这一项往往是升级时最先爆出来的报错来源。建议先全局搜索 `@Type(` 和 `std.meta.`,再逐个迁移。 ### 小整数类型现在可以安全地隐式转换为浮点 @@ -196,12 +409,43 @@ fn foo() *i32 { } ``` +报错形式如下: + +```sh +test.zig:3:13: error: returning address of expired local variable 'x' + return &x; + ^ +test.zig:2:9: note: declared runtime-known here + var x: i32 = 1234; + ^ +``` + +这个变化背后有点小故事。返回无效指针本身在 Zig 里是合法的——非法只发生在解引用: + +```zig +fn foo() *i32 { + return undefined; +} +``` + +甚至下面这种“无条件触发非法行为”的函数也是合法的: + +```zig +fn bar() noreturn { + unreachable; // equivalent to foo().* +} +``` + +也就是说 `bar()` 的语义等价于 `unreachable`。那要怎么把“返回局部变量地址”变成编译错误?答案是“句法层面的较真”:编译器禁止所有“不需要类型检查就能 trivially 降级为 `return undefined` 的表达式”,理由是这种代码应当写成规范的 `return undefined`。`return &x;`(其中 `x` 是局部变量)正属于这一类。 + 如果你在升级后遇到这类错误,正确修复方式通常是三种之一: - 直接返回值,而不是返回指针 - 让调用方传入 buffer / 输出参数 - 改用堆分配,并明确约定释放责任 +后续官方计划继续以同样的思路加入更多类似的编译错误。 + ### `packed` 与 `extern` 规则更严格 #### `packed union` 需要明确 backing integer,并保证各字段 bit size 一致 @@ -231,6 +475,34 @@ const U = packed union(u16) { 总结一下这条规则:如果你需要 `packed union`,就请明确写出 backing integer,并保证每个字段都能映射到同样大小的位表示。 +之前 `packed struct(T)` 已经可以指定 backing integer,但 `packed union(T)` 不行;`0.16.0` 把这条限制也去掉了。下面是用普通声明语法和用 `@Union` builtin 同时构造同一个 packed union 的例子: + +```zig +// 用普通声明语法 +const Split16 = packed union(u16) { + raw: MaybeSigned16, + split: packed struct { low: u8, high: u8 }, +}; + +// 用 `@Union` builtin +const MaybeSigned16 = @Union( + .@"packed", + u16, // backing integer + &.{ "unsigned", "signed" }, + &.{ u16, i16 }, + &@splat(.{}), +); + +test "use packed union type with explicit backing integer" { + const u: Split16 = .{ .raw = .{ .unsigned = 0xFFFE } }; + try testing.expectEqual(-2, u.raw.signed); + try testing.expectEqual(0xFE, u.split.low); + try testing.expectEqual(0xFF, u.split.high); +} +``` + +由于下面 “`extern` 场景必须显式指定 tag type / backing type” 这条规则的存在,现在某些场景下显式指定 packed union 的 backing integer 是必须的。 + #### `packed struct` / `packed union` 不再允许指针字段 如果你过去把指针直接塞进 `packed` 类型里,现在需要改成整数保存地址: @@ -282,6 +554,78 @@ const x: i32 = @round(value); 这项改动本身不一定会让旧代码报错,但会让很多“先取整、再转整数”的写法明显简化。 +### 一元浮点内建会向下转发结果类型 + +`@sqrt`、`@sin`、`@cos`、`@tan`、`@exp`、`@exp2`、`@log`、`@log2`、`@log10`、`@floor`、`@ceil`、`@trunc`、`@round` 现在都会把外层的结果类型向内转发,于是下面这种过去无法直接写的式子在 `0.16.0` 是合法的: + +```zig +const x: f64 = @sqrt(@floatFromInt(N)); +``` + +之前 `@sqrt` 不会把 `f64` 这个结果类型传给内层的 `@floatFromInt`,所以你必须手工加一层中间变量。这项改动不会让旧代码报错,但能消除大量样板。 + +### `*u8` 与 `*align(1) u8` 不再是同一个类型 + +在 `0.16.0` 之前,Zig 把这两者视为完全相同的类型(甚至 `==` 比较都为真)。从这个版本开始,它们变成了两个不同的类型。 + +不过**两者仍然可以互相强转,包括嵌套在指针里的“in-memory coercion”**,所以日常用法绝大多数情况下不需要任何改动。可以把这种区分理解为类似 `u32` 与 `c_uint` 的关系:技术上不同,但行为一致。 + +只有在你显式比较 `@TypeOf(...)` 的相等性、或者依赖 `@typeInfo` 反射时,才需要顺手处理一下。 + +### Zero-bit tuple 字段不再被隐式标记为 `comptime` + +`0.14` 时引入的“tuple 中 zero-bit 字段自动变成 `comptime` 字段”这个隐式规则在 `0.16.0` 被回滚。也就是说: + +```zig +const S = struct { void }; +@typeInfo(S).@"struct".fields[0].is_comptime +// 0.15.x 下为 true,0.16.0 下为 false +``` + +这个改动几乎不会影响任何真实代码,因为 zero-bit 字段的值依然是 comptime-known 的。但如果你直接读 `std.builtin.StructField.is_comptime`,或者依赖“带 / 不带 `comptime` 的 tuple 互为同一类型”的写法,就需要相应调整。 + +### 字段分析变成 lazy + +引入新的 `std.Io` 接口之后,官方注意到一个问题:只要把某个类型当成命名空间使用,它的字段也会被分析。例如代码里用了 `std.Io.Writer`,就会把 `std.Io` 的整个 vtable 拉进来——某些场景下这甚至会引入不必要的 codegen,让产物体积明显膨胀。 + +`0.16.0` 把 `struct`(提醒一下,`.zig` 文件本身就是 struct)、`union`、`enum`、`opaque` 改成只在“需要它的尺寸”或“需要它的某个字段类型”时才解析字段。这意味着: + +- 把类型当作命名空间引用时,不会触发字段解析 +- 即便构造非解引用指针 `*T`,只要从不实际取尺寸或字段,`T` 也不会被解析 + +这是一项内部行为优化,对绝大多数代码透明。但如果你以前刻意依赖“引用一个类型就能强迫它被解析”这种隐式行为(例如某些 trait 风格的元编程),可能需要显式触发字段解析(比如调用 `@sizeOf(T)` 或访问一个具体字段)。这条改动是 `Reworked Type Resolution` 的一部分。 + +### 指向 comptime-only 类型的指针不再是 comptime-only + +虽然 `comptime_int` 是 comptime-only 类型,但在 `0.16.0` 里: + +- `*comptime_int` 不是 comptime-only +- `[]comptime_int` 也不是 comptime-only + +最直观的例子是函数指针:`*const fn () void` 是运行时类型——你不能在运行时解引用它,因为元素类型 `fn () void` 是 comptime-only;但这个指针本身可以在运行时存在。也就是说,这类指针“可以在运行时存在,但只能在编译期解引用”。 + +这条规则在反射场景下意外地有用。比如你拿到一个 `[]const std.builtin.Type.StructField`,想把每个字段的 `name` 传给运行时代码。 + +旧写法:先把 `name` 抽成一个 `[]const []const u8`,再把后者传给运行时函数。 + +新写法:可以直接把 `[]const std.builtin.Type.StructField` 传给运行时函数。这个函数自然不能在运行时从切片里加载一个 `StructField`(毕竟 `StructField` 是 comptime-only),但**可以**加载它的 `name` 字段——因为 `name` 的类型是运行时类型! + +这条改动同样是 `Reworked Type Resolution` 的一部分。 + +### Reworked Byval 语法降级 + +这是一条编译器内部的改动,但因为它直接影响 `Forbid Runtime Vector Indexes` 等语义,所以值得在升级时知道。 + +编译器前端早期为了减少中间指令数,曾经尝试用“byval”语义降级表达式。这个实验最后被认定为失败,因为它带来了: + +- 数组访问的性能问题 +- 显式拷贝下出现意料之外的别名 +- 退化场景里代码质量极差 + +`0.16.0` 改成全程“byref”降级,只在最终一次 load 时才取值。这不仅修掉了上面这些问题,也是为什么本版本会顺手禁止运行时向量索引(详见前面对应小节)。 + +对应用代码而言,这条改动总体是无感的——但如果你以前观察到“某些数组 / 向量代码有奇怪的别名表现”,升级到 `0.16.0` 后大概率会自动消失。 + ### 类型解析规则被重做,依赖环错误会更清晰 `0.16.0` 彻底重做了编译器内部的类型解析流程。结果是: @@ -290,12 +634,70 @@ const x: i32 = @round(value); - 一些以前会莫名其妙报 dependency loop 的代码,现在反而能正常工作 - 也有一小部分旧代码会因为真正的依赖环而在 `0.16.0` 开始报错 -最常见的症状,是结构体默认字段值、对 `@This()` 的对齐查询、`@typeInfo` 反射等路径互相依赖。 +最常见的两类“现在会报错”的写法: + +1. **结构体在自身字段上做对齐查询**: + + ```zig + const S = struct { + foo: [*]align(@alignOf(@This())) u8, + }; + ``` -这类问题没有统一的机械式修法,但 `0.16.0` 的错误信息比以前清楚很多,通常你只需要打断这条环上的任意一条依赖即可。 + `@alignOf(@This())` 需要先知道 `S` 的对齐,但 `S` 的对齐又依赖这个字段的对齐——直接构成依赖环,这版编译器会直接给出明确的错误:`type 'S' depends on itself for alignment query here`。 + +2. **结构体默认字段值与 `@typeInfo` 反射形成环**: + + ```zig + const S = struct { x: u32 = default_val }; + const default_val = other_val; + const other_val = @typeInfo(S).@"struct".fields.len; + ``` + + 编译器现在会按依赖顺序把每一跳都列出来,并提示“破坏其中任何一条都能解开环”。 + +这类问题没有统一的机械式修法,但 `0.16.0` 的错误信息比以前清楚很多,通常你只需要打断这条环上的任意一条依赖即可。如果实在排查不出来,建议加入 Zig 社区交流。 ## 标准库 +### 标准库总览:新增 / 移除 / 错误集合改名 + +具体迁移点之外,先把 `0.16.0` 在标准库根命名空间一级的几类总体调整列出来,方便你在升级时一次性扫一遍。 + +新增: + +- `Io.Dir.renamePreserve`:不会替换目标文件的 rename 操作 +- `Io.net.Socket.createPair` + +直接移除(不再有替代): + +- `SegmentedList` +- `meta.declList` +- `Io.GenericWriter` / `Io.AnyWriter` / `Io.null_writer` +- `Io.CountingReader` +- `Thread.Mutex.Recursive` + +错误集合改名 / 合并: + +- `error.RenameAcrossMountPoints` ➡️ `error.CrossDevice` +- `error.NotSameFileSystem` ➡️ `error.CrossDevice` +- `error.SharingViolation` ➡️ `error.FileBusy` +- `error.EnvironmentVariableNotFound` ➡️ `error.EnvironmentVariableMissing` +- `std.Io.Dir.rename` 现在返回 `error.DirNotEmpty` 而不是 `error.PathAlreadyExists` + +其它零散调整: + +- `fmt.Formatter` ➡️ `fmt.Alt` +- `fmt.format` ➡️ `std.Io.Writer.print` +- `fmt.FormatOptions` ➡️ `fmt.Options` +- `fmt.bufPrintZ` ➡️ `fmt.bufPrintSentinel` +- `compress`:`lzma` / `lzma2` / `xz` 已迁到 `Io.Reader` / `Io.Writer` +- `DynLib`:Windows 支持被移除——用户应该直接使用 `LoadLibraryExW` 和 `GetProcAddress`(实际多数人本来就是这么做的) +- `math.sign`:现在返回能容纳所有可能值的最小整数类型 +- Windows 上现在会自动触发拉取根证书 +- `tar.extract`:对路径穿越进行清理 +- `BitSet` / `EnumSet`:`initEmpty` / `initFull` 改为 decl literal + ### `std.Io` 成为新的核心 I/O 抽象 这是 `0.16.0` 最大的标准库变更。现在,凡是“可能阻塞控制流”或“会引入非确定性”的能力,基本都要求显式接收一个 `std.Io` 实例。 @@ -413,10 +815,27 @@ const result = try std.process.run(allocator, io, .{ 替换当前进程镜像: +```zig +const err = std.process.execv(arena, argv); +``` + +⬇️ + ```zig const err = std.process.replace(io, .{ .argv = argv }); ``` +### 网络 API 全面迁到 `std.Io` + +`std.net` 下的所有网络 API 都被迁到了 `std.Io`,统一通过 `Io` 实例发起调用。Windows 下的网络实现也彻底改成直接基于 AFD,不再依赖 `ws2_32.dll`,因此 cancelation 和 Batch 在 Windows 上也能正确工作。 + +需要注意: + +- `Io.Evented` 目前**还没有实现网络**,依赖 evented 网络的代码暂时只能选 `Io.Threaded` 或其它实现 +- `Io.net` 当前**也还缺乏非 IP 协议的能力** + +如果你过去使用 `std.net.Stream` / `std.net.tcpConnectToHost` 等 API,请按相同思路改为接收 `io: std.Io` 并使用 `std.Io.net.*`;具体迁移建议关注后续标准库文档,因为这部分接口还在快速演进中。 + ### `std.Thread.Pool` 被移除 `std.Thread.Pool` 已经从标准库中移除。最常见的迁移方向,是改用 `std.Io.async` 或 `std.Io.Group.async`。 @@ -435,6 +854,138 @@ fn doAllTheWork(io: std.Io) !void { 另外要特别注意:如果你的旧代码里除了 `Thread.Pool` 之外,还用了 `Thread.Mutex`、`Thread.Condition`、`Thread.ResetEvent` 等同步原语,那么升级到 `std.Io` 时,它们也应该一起迁到对应的 `std.Io.*` 类型。 +### 同步原语全面迁到 `std.Io` + +`0.16.0` 中,绝大多数同步原语都从 `std.Thread` 迁到了 `std.Io` 命名空间。这样做的核心原因是:**被同步的代码必须能与应用所选的 I/O 实现正确集成**——只有这样,等待行为才能跟随当前 `Io` 实现走。例如,在 `Io.Threaded` 下争用的 mutex 会阻塞线程;在 `Io.Evented` 下则会切换栈而不是阻塞当前线程。这些同步原语同样会正确接入新的 cancelation 模型。 + +需要注意的是,纯 lock-free 的同步原语并不需要接入 `std.Io`。 + +完整迁移表: + +- `std.Thread.ResetEvent` ➡️ `std.Io.Event` +- `std.Thread.WaitGroup` ➡️ `std.Io.Group` +- `std.Thread.Futex` ➡️ `std.Io.Futex` +- `std.Thread.Mutex` ➡️ `std.Io.Mutex` +- `std.Thread.Condition` ➡️ `std.Io.Condition` +- `std.Thread.Semaphore` ➡️ `std.Io.Semaphore` +- `std.Thread.RwLock` ➡️ `std.Io.RwLock` +- `std.once` 已被移除,建议直接避免全局变量,或者自行实现等价逻辑 + +### 随机数与熵 API 接入 `std.Io` + +随机数生成相关接口在 `0.16.0` 也被收敛到了 `std.Io`:日常熵直接通过 `io.random` 获取,而需要绕开进程内 RNG 状态、强制走 syscall 的“安全熵”则改用 `io.randomSecure`。 + +`std.crypto.random.bytes` 迁移: + +```zig +var buffer: [123]u8 = undefined; +std.crypto.random.bytes(&buffer); +``` + +⬇️ + +```zig +var buffer: [123]u8 = undefined; +io.random(&buffer); +``` + +需要 `std.Random` 接口时: + +```zig +const rng = std.crypto.random; +``` + +⬇️ + +```zig +const rng_impl: std.Random.IoSource = .{ .io = io }; +const rng = rng_impl.interface(); +``` + +`posix.getrandom` 也统一走 `io.random`: + +```zig +var buffer: [64]u8 = undefined; +posix.getrandom(&buffer); +``` + +⬇️ + +```zig +var buffer: [64]u8 = undefined; +io.random(&buffer); +``` + +另外,`std.Options.crypto_always_getrandom` 和 `std.Options.crypto_fork_safety` 这两个全局选项也被移除了,对应能力变成了 `io.random` / `io.randomSecure` 两条不同的 API 路径。 + +### 时间 API 迁到 `std.Io` + +时间相关接口也并入 `std.Io`,并允许查询时钟分辨率(可能失败)。`error.Unexpected` 和 `error.ClockUnsupported` 因此从超时和时钟读取的错误集合里被剔除——分辨率被视为无穷大,由用户自行通过 `Clock.resolution` 单独检查。 + +迁移表: + +- `std.time.Instant` ➡️ `std.Io.Timestamp` +- `std.time.Timer` ➡️ `std.Io.Timestamp` +- `std.time.timestamp` ➡️ `std.Io.Timestamp.now` + +### `{D}` 格式说明符被移除 + +为了配合新的 `std.Io.Duration` 类型并增强类型安全,`{D}` 这个旧的 duration 格式说明符已经被移除: + +```zig +writer.print("{D}", .{ns}); +``` + +⬇️ + +```zig +writer.print("{f}", .{std.Io.Duration{ .nanoseconds = ns }}); +``` + +### 调试栈追踪 API 重做 + +`0.16.0` 重做了一批调试相关 API,特别是栈追踪。核心目标是:**在不依赖帧指针(如 libc 用 `-fomit-frame-pointer` 编译)的情况下,也能实现快速且不会因为越界访问而崩溃的栈展开**。这其实是个很复杂的问题,真正的解法是 unwind information,而不同目标对 unwind 信息的编码方式各不相同;旧实现既 buggy 又不完整,而且常常拖慢性能。 + +从这个版本开始,标准库**默认使用基于 unwind 信息的“安全”栈展开**;和原来基于帧指针的展开相比,性能开销在大多数场景下是可以接受的。 + +主要 API 变化: + +- `captureStackTrace` ➡️ `captureCurrentStackTrace` +- `dumpStackTraceFromBase` ➡️ `dumpCurrentStackTrace` +- `walkStackWindows` ➡️ `captureCurrentStackTrace` +- `writeStackTraceWindows` ➡️ `writeCurrentStackTrace` +- `std.debug.StackIterator` 现在是内部 API,已从公开导出中移除 + +新 API 签名示例: + +```zig +/// 把已经捕获的栈追踪写入 `t`,并附上源码位置。 +pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void { ... } + +/// 捕获当前栈追踪到 `addr_buf`。`addr_buf` 的生命周期需要不短于返回的 StackTrace。 +pub noinline fn captureCurrentStackTrace( + options: StackUnwindOptions, + addr_buf: []usize, +) StackTrace { ... } +``` + +`StackUnwindOptions` 提供了几个常用选项: + +- `first_address`:忽略到指定返回地址前的所有栈帧(典型用法是把 panic handler 自身从 trace 里抹掉) +- `context`:从指定的 `cpu_context.Native` 而不是当前栈顶开始展开(典型用法是从信号处理函数里打印 trace) +- `allow_unsafe_unwind`:作为最后手段,允许使用可能崩溃的展开策略;默认为 `false` + +绝大多数情况下用 `captureCurrentStackTrace` 就够了;如果需要打印当前栈,对应的还有 `writeCurrentStackTrace` / `dumpCurrentStackTrace`。`StackIterator` 已经不再适合直接使用,如果你以前依赖它,可以考虑直接用 `std.debug.SelfInfo` 提供的更底层 API;后者还可以通过定义 `@import("root").debug.SelfInfo` 替换实现,从而让栈追踪在标准库不直接支持的目标(甚至 freestanding 目标)上也能工作。 + +### `ucontext_t` 与相关类型 / 函数被移除 + +`std.posix` 不再提供 `ucontext_t` 系列绑定。原因有两条: + +- 用 `ucontext.h` 函数做非局部控制流的能力本来就不在 Zig 支持范围内,且 POSIX 已弃用,musl 也不再提供 +- 用 `ucontext_t` 在信号处理函数里读取机器状态这件事,标准库做得很差——这类类型在不同架构下变化很快,标准库一直没跟上 + +如果你的代码确实需要在信号处理里访问机器状态,建议自行定义贴合具体场景的 `ucontext_t` / `mcontext_t`。`std.debug.cpu_context.signal_context_t` 也在这一版本里相应调整。 + ### `std.io` 进一步收敛到 `std.Io` 这一轮更新里,`GenericReader`、`AnyReader`、`FixedBufferStream` 等历史接口继续退出。 @@ -475,6 +1026,111 @@ var writer: std.Io.Writer = .fixed(buffer); ### 文件系统和路径 API 有一批实用迁移点 +`fs` 全部 API 都迁到了 `Io`。和 0.15 那次 “writergate” 不同,这次虽然 breaking 范围很大,但绝大多数迁移机械、不需要特别多的判断。最典型的形态就是给原本无参的方法加一个 `io`: + +```zig +file.close(); +``` + +⬇️ + +```zig +file.close(io); +``` + +升级 diff 可能很长,但每一处都很容易看懂。 + +新增 API: + +- `Io.Dir.hardLink` +- `Io.Dir.Reader` +- `Io.Dir.setFilePermissions` +- `Io.Dir.setFileOwner` +- `Io.File.NLink` + +无对应替代被直接移除的 API: + +- `fs.realpathZ` / `fs.realpathW` / `fs.realpathW2` +- `fs.makeDirAbsoluteZ` / `fs.deleteDirAbsoluteZ` / `fs.openDirAbsoluteZ` +- `fs.renameAbsoluteZ` / `fs.renameZ` +- `fs.deleteTreeAbsolute` +- `fs.symLinkAbsoluteW` +- `fs.Dir.realpathZ` / `fs.Dir.realpathW` / `fs.Dir.realpathW2` +- `fs.Dir.deleteFileZ` / `fs.Dir.deleteFileW` / `fs.Dir.deleteDirZ` / `fs.Dir.deleteDirW` +- `fs.Dir.renameZ` / `fs.Dir.renameW` +- `fs.Dir.symLinkWasi` / `fs.Dir.symLinkZ` / `fs.Dir.symLinkW` +- `fs.Dir.readLinkWasi` / `fs.Dir.readLinkZ` / `fs.Dir.readLinkW` +- `fs.Dir.adaptToNewApi` / `fs.Dir.adaptFromNewApi` +- `fs.File.isCygwinPty` +- `fs.File.adaptToNewApi` / `fs.File.adaptFromNewApi` + +重命名 / 迁移过的 API(节选最常用的部分): + +| 0.15.x | 0.16.0 | +| --- | --- | +| `fs.Dir` | `std.Io.Dir` | +| `fs.File` | `std.Io.File` | +| `fs.cwd` | `std.Io.Dir.cwd` | +| `fs.copyFileAbsolute` | `std.Io.Dir.copyFileAbsolute` | +| `fs.makeDirAbsolute` | `std.Io.Dir.createDirAbsolute` | +| `fs.deleteDirAbsolute` | `std.Io.Dir.deleteDirAbsolute` | +| `fs.openDirAbsolute` | `std.Io.Dir.openDirAbsolute` | +| `fs.openFileAbsolute` | `std.Io.Dir.openFileAbsolute` | +| `fs.accessAbsolute` | `std.Io.Dir.accessAbsolute` | +| `fs.createFileAbsolute` | `std.Io.Dir.createFileAbsolute` | +| `fs.deleteFileAbsolute` | `std.Io.Dir.deleteFileAbsolute` | +| `fs.renameAbsolute` | `std.Io.Dir.renameAbsolute` | +| `fs.readLinkAbsolute` | `std.Io.Dir.readLinkAbsolute` | +| `fs.symLinkAbsolute` | `std.Io.Dir.symLinkAbsolute` | +| `fs.realpath` | `std.Io.Dir.realPathFileAbsolute` | +| `fs.realpathAlloc` | `std.Io.Dir.realPathFileAbsoluteAlloc` | +| `fs.rename` | `std.Io.Dir.rename` | +| `fs.has_executable_bit` | `std.Io.File.Permissions.has_executable_bit` | +| `fs.defaultWasiCwd` | `std.os.defaultWasiCwd` | +| `fs.openSelfExe` | `std.process.openExecutable` | +| `fs.selfExePath` | `std.process.executablePath` | +| `fs.selfExePathAlloc` | `std.process.executablePathAlloc` | +| `fs.selfExeDirPath` | `std.process.executableDirPath` | +| `fs.selfExeDirPathAlloc` | `std.process.executableDirPathAlloc` | +| `fs.Dir.setAsCwd` | `std.process.setCurrentDir` | +| `fs.Dir.realpath` | `std.Io.Dir.realPathFile` | +| `fs.Dir.realpathAlloc` | `std.Io.Dir.realPathFileAlloc` | +| `fs.Dir.makeDir` | `std.Io.Dir.createDir` | +| `fs.Dir.makePath` | `std.Io.Dir.createDirPath` | +| `fs.Dir.makeOpenDir` | `std.Io.Dir.createDirPathOpen` | +| `fs.Dir.atomicSymLink` | `std.Io.Dir.symLinkAtomic` | +| `fs.Dir.chmod` | `std.Io.Dir.setPermissions` | +| `fs.Dir.chown` | `std.Io.Dir.setOwner` | +| `fs.File.Mode` | `std.Io.File.Permissions` | +| `fs.File.PermissionsWindows` | `std.Io.File.Permissions` | +| `fs.File.PermissionsUnix` | `std.Io.File.Permissions` | +| `fs.File.default_mode` | `std.Io.File.Permissions.default_file` | +| `fs.File.getOrEnableAnsiEscapeSupport` | `std.Io.File.enableAnsiEscapeCodes` | +| `fs.File.setEndPos` | `std.Io.File.setLength` | +| `fs.File.getEndPos` | `std.Io.File.length` | +| `fs.File.seekTo` / `seekBy` / `seekFromEnd` | `std.Io.File.Reader.seekTo` / `Reader.seekBy` / `Writer.seekTo` | +| `fs.File.getPos` | `std.Io.File.Reader.logicalPos` / `std.Io.Writer.logicalPos` | +| `fs.File.mode` | `std.Io.File.stat().permissions.toMode` | +| `fs.File.chmod` | `std.Io.File.setPermissions` | +| `fs.File.chown` | `std.Io.File.setOwner` | +| `fs.File.updateTimes` | `std.Io.File.setTimestamps` / `setTimestampsNow` | +| `fs.File.read` / `readv` | `std.Io.File.readStreaming` | +| `fs.File.pread` / `preadv` | `std.Io.File.readPositional` | +| `fs.File.preadAll` | `std.Io.File.readPositionalAll` | +| `fs.File.write` / `writev` | `std.Io.File.writeStreaming` | +| `fs.File.pwrite` / `pwritev` | `std.Io.File.writePositional` | +| `fs.File.writeAll` | `std.Io.File.writeStreamingAll` | +| `fs.File.pwriteAll` | `std.Io.File.writePositionalAll` | +| `fs.File.copyRange` / `copyRangeAll` | `std.Io.File.writer` | + +这一表里许多函数除了改名,还顺手在签名里塞了一个 `io: std.Io` 参数。 + +另外这些三个老的命名空间常量被 deprecated: + +- `fs.path` ➡️ `std.Io.Dir.path` +- `fs.max_path_bytes` ➡️ `std.Io.Dir.max_path_bytes` +- `fs.max_name_bytes` ➡️ `std.Io.Dir.max_name_bytes` + #### `readFileAlloc` 旧写法: @@ -572,11 +1228,143 @@ try file.setTimestamps(io, .{ }); ``` +#### 选择性遍历目录树 + +旧的 `Dir.walk` 没法跳过特定子目录。`0.16.0` 新增了 `walkSelectively`,每次进入新目录都需要显式 `enter`,从而避免对被跳过目录做无谓的 open/close syscall。 + +```zig +var walker = try dir.walk(gpa); +defer walker.deinit(); + +while (try walker.next(io)) |entry| { + // ... +} +``` + +⬇️ + +```zig +var walker = try dir.walkSelectively(gpa); +defer walker.deinit(); + +while (try walker.next(io)) |entry| { + if (failsFilter(entry)) continue; + if (entry.kind == .directory) { + try walker.enter(io, entry); + } + // ... +} +``` + +另外 `Walker.Entry` 增加了 `depth` 函数,`Walker` 与 `SelectiveWalker` 都增加了 `leave`,便于在遍历到一半时跳出当前子目录。 + +#### `fs.path` 对 Windows 路径处理更一致 + +`std.fs.path` 全部函数都更正确地处理 Windows 的 UNC、"rooted" 和 drive-relative 路径。API 上的具体变化: + +- `windowsParsePath` / `diskDesignator` / `diskDesignatorWindows` ➡️ `parsePath` / `parsePathWindows` / `parsePathPosix` +- 新增 `getWin32PathType` +- `componentIterator` / `ComponentIterator.init` 不再返回错误 + +#### `File.MemoryMap` 语义收紧 + +内存映射的指针内容现在被定义为只在显式 sync point 后同步,这让基于普通文件操作的回退实现成为合法选择,也允许 evented I/O 用 evented 文件 I/O 来实现 sync point。 + +技术上这是 breaking change:positional 文件读写的错误集合更窄;在 WASI 上现在会正确返回 `error.IsDir` 而不是 `error.NotOpenForReading`。 + +#### 内存锁定 / 保护 API 迁到 `std.process` + +`mmap` / `mprotect` 的标志现在改为类型安全的结构体字段: + +```zig +std.posix.PROT.READ | std.posix.PROT.WRITE, +``` + +⬇️ + +```zig +.{ .READ = true, .WRITE = true }, +``` + +`mlock` 系列也搬到了 `std.process`: + +```zig +try std.posix.mlock(); +try std.posix.mlock2(slice, std.posix.MLOCK_ONFAULT); +try std.posix.mlockall(slice, std.posix.MCL_CURRENT|std.posix.MCL_FUTURE); +``` + +⬇️ + +```zig +try std.process.lockMemory(slice, .{}); +try std.process.lockMemory(slice, .{ .on_fault = true }); +try std.process.lockMemoryAll(.{ .current = true, .future = true }); +``` + +#### "Preopens" 迁到 `std.process` + +WASI 上预先打开的文件句柄从 `std.fs.wasi.Preopens` 搬到了 `std.process.Preopens`: + +```zig +const wasi_preopens: std.fs.wasi.Preopens = try .preopensAlloc(arena); +``` + +⬇️ + +```zig +const preopens: std.process.Preopens = try .init(arena); +``` + +或者直接通过前面 `Juicy Main` 部分介绍的 `std.process.Init.preopens` 拿到。在非 WASI 系统上数据类型是 `void`——你不用就不会付出代价。 + +#### `atomicFile` 重构为 `createFileAtomic` + +这次重构主要动机是把 `std.crypto.random` 的调用挪到 `std.Io.VTable` 之下(具体是 `std.Io.File.Atomic.init` 里那一处),同时顺手在 Linux 上接入了 `O_TMPFILE`——也就是“创建一个无名 fd,操作完后再 link 到目标位置;如果进程提前结束,OS 会自动回收,不留临时垃圾”这套机制。 + +不过 `O_TMPFILE` 在内核 / libc 这一层有不少坑(`linkat()` 缺 `AT_REPLACE`、错误码为 `EISDIR`/`ENOENT` 这种反直觉行为等),所以当前实现是“能用上 `O_TMPFILE` 的场景用,其余场景仍然走原来的随机文件名 + `renameat()`”。这套封装让 Zig 端不用感知差异,将来 OS 修了 bug 直接受益。 + +旧写法: + +```zig +var buffer: [1024]u8 = undefined; +var atomic_file = try dest_dir.atomicFile(io, dest_path, .{ + .permissions = actual_permissions, + .write_buffer = &buffer, +}); +defer atomic_file.deinit(); + +// do something with atomic_file.file_writer; + +try atomic_file.flush(); +try atomic_file.renameIntoPlace(); +``` + +⬇️ + +```zig +var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{ + .permissions = actual_permissions, + .make_path = true, + .replace = true, +}); +defer atomic_file.deinit(io); + +var buffer: [1024]u8 = undefined; // 仅在没有直接 fd-to-fd 通路时使用 +var file_writer = atomic_file.file.writer(io, &buffer); + +// do something with file_writer + +try file_writer.flush(); +try atomic_file.replace(io); // 或者把上面的 .replace 改为 false,再调用 link() +``` + +另外这一版还新增了 `std.Io.File.hardLink` API(目前仅 Linux)——它是 `O_TMPFILE` 没有 replace 语义时把 fd 物化为常规文件的必备工具。 + #### 其他值得顺手处理的文件系统改动 -- `std.fs.wasi.Preopens` ➡️ `std.process.Preopens` -- 原来的 `atomicFile` 流程重构为 `createFileAtomic` -- `fs.getAppDataDir` 已被移除,应用应自行决定“应用数据目录”的策略 +- `fs.getAppDataDir` 已被移除,应用应自行决定“应用数据目录”的策略;可考虑第三方包 `known-folders` +- `Io.Writer.Allocating` 新增了 `alignment: std.mem.Alignment` 字段(运行时已知对齐,配合 Allocator API 的 raw 函数变体使用) ### `std.posix` 和 `std.os.windows` 的中层 API 被移除 @@ -599,13 +1387,36 @@ try file.setTimestamps(io, .{ 这次比较关键的变化有: +- 新增 `heap.MemoryPoolUnmanaged` / `heap.MemoryPoolAlignedUnmanaged` / `heap.MemoryPoolExtraUnmanaged` +- `PriorityDequeue` 不再持有 `Allocator` 字段 +- `PriorityQueue` 不再持有 `Allocator` 字段 - `ArrayHashMap`、`AutoArrayHashMap`、`StringArrayHashMap` 被移除 - `AutoArrayHashMapUnmanaged` ➡️ `std.array_hash_map.Auto` - `StringArrayHashMapUnmanaged` ➡️ `std.array_hash_map.String` - `ArrayHashMapUnmanaged` ➡️ `std.array_hash_map.Custom` - `PriorityQueue` 和 `PriorityDequeue` 都继续往 `.empty` / `push` / `pop` 风格迁移 -`PriorityQueue` 常见重命名: +`PriorityQueue` 现在可以通过 `.empty` 初始化(不再需要 `init` 方法),最小堆和最大堆只需要换比较函数即可: + +```zig +fn lessThan(context: void, a: u32, b: u32) Order { + _ = context; + return std.math.order(a, b); +} +const MinHeap = std.PriorityQueue(u32, void, lessThan); +var queue: MinHeap = .empty; +``` + +```zig +fn greaterThan(context: void, a: u32, b: u32) Order { + _ = context; + return std.math.order(a, b).invert(); +} +const MaxHeap = std.PriorityQueue(u32, void, greaterThan); +var queue: MaxHeap = .empty; +``` + +常见重命名: - `init` ➡️ `initContext` - `add` ➡️ `push` @@ -615,7 +1426,9 @@ try file.setTimestamps(io, .{ - `removeOrNull` ➡️ `pop` - `removeIndex` ➡️ `popIndex` -`PriorityDequeue` 常见重命名: +`PriorityDequeue` 的改动整体跟随 `Deque`:含 `add` 的方法改名为 `push`,含 `remove` 的方法改名为 `pop`;`popMinOrNull` / `popMaxOrNull` 与 `popMin` / `popMax` 合并(功能不变);默认字段值通过 `.empty` 常量而不是 `init()` 方法初始化。 + +常见重命名: - `init` ➡️ `.empty` - `add` ➡️ `push` @@ -629,11 +1442,83 @@ try file.setTimestamps(io, .{ 有两条需要直接注意: -- `std.heap.ArenaAllocator` 现在变成了 thread-safe 且 lock-free -- `std.heap.ThreadSafeAllocator` 被移除 +- `std.heap.ArenaAllocator` 现在变成了 thread-safe 且 lock-free。它在单线程场景下的性能与旧实现相当,在最多 7 线程并发的场景下,则比“旧实现 + `ThreadSafeAllocator` 包装”略快;同样地,未来 `std.heap.DebugAllocator` 也会朝这个方向走 +- `std.heap.ThreadSafeAllocator` 被移除——“mutex 包一层 allocator”这种实现既必然要求 `Io` 实例又通常很慢,已经被官方视为反模式 如果你的旧代码是“在外层包一层 `ThreadSafeAllocator`”,现在应改为直接选用本身适合并发场景的 allocator,或者改造调用结构,避免再依赖这层包装器。 +### Deflate 压缩与解压重做 + +`std.compress.flate` 这一轮**新增了从零实现的 deflate 压缩器**: + +- 默认 writer:以 writer 缓冲区作为历史窗口、用链式哈希表寻找匹配,token 累积到阈值后整块输出 +- 新增 `Raw` writer:完全只输出 store block(即未压缩字节),借助数据向量高效发送 block header 与数据 +- 新增 `Huffman` writer:只做 Huffman 压缩,不做匹配 + +`Raw` 与 `Huffman` 因为不需要保留历史,可以更直接地利用新的 writer 语义。 + +`token` 中的字面量与距离编码参数也被重做:参数现在是数学方法推导出来的,更昂贵的那部分依然走查表(`ReleaseSmall` 例外)。 + +解压侧的 bit 读取也大幅简化,充分利用了底层 reader 可以 peek 的能力,并修掉了若干和 limit 处理相关的 bug。 + +性能数据(与 zlib 对比,越快越好): + +- **默认级别压缩**:std-deflate 比 zlib 快约 **9.7%**,cache miss 与分支误预测都明显更少 +- **最高级别压缩**:与 zlib 持平(差异 1% 以内) +- **解压(vs 上一版 std)**:新实现快约 **9.5%**,CPU 周期与指令数都减少约 10% + +压缩比层面,zlib 在默认级别下高约 1.00%,最高级别下高约 0.77%——这是后续打磨的方向。 + +### `std.crypto` 新增 AES-SIV 与 AES-GCM-SIV + +针对 nonce 重用敏感的场景,`std.crypto` 现在内置了: + +- **AES-SIV**:在密钥包装(key wrapping)这类场景里特别有用 +- **AES-GCM-SIV**:在嵌入式 / 受限目标上尤其合适 + +如果你的项目以前不得不靠第三方 crate 提供这两个原语,现在可以直接换到标准库版本。 + +### `std.crypto` 新增 Ascon-AEAD / Ascon-Hash / Ascon-CHash + +NIST SP 800-232 已经发布之后,Zig 标准库一次性补齐基于 Ascon 置换的高层构造: + +- `Ascon-AEAD`:AEAD 加密 +- `Ascon-Hash`:定长哈希 +- `Ascon-CHash`:可定制化的哈希 + +之前 Ascon 置换本身已经在标准库里,但建立在它之上的高层构造一直被刻意推迟到 NIST 最终规范公布。现在终于可以在标准库内直接使用。 + +### `std.Progress` 支持 Windows 跨进程上报 + +`std.Progress` 现在支持在 Windows 下报告子进程的进度,子进程的进度会自动反映到父进程的进度树中。同时,最大节点名称长度从 40 提升到 120。 + +如果你以前因为 Windows 下 progress 不能跨进程聚合而做了 workaround,可以删掉那部分代码。 + +### Windows 上网络 API 不再依赖 `ws2_32.dll` + +Windows 上所有网络 API 现在直接基于 AFD 实现,不再走 `ws2_32.dll`。这意味着: + +- 一批网络相关历史 bug 被修复 +- cancelation 与 Batch 在 Windows 上能正确生效 +- 避开了 `ws2_32.dll` 的性能陷阱(例如 socket handle 旁挂的 hash table) + +对应用层代码这通常是透明的,但如果你以前手写了 `extern "ws2_32"` 的绑定来补标准库不足,现在可以重新评估是否还需要继续维护这部分代码。 + +### Windows 上向 NtDll 的迁移基本完成 + +`0.16.0` 之后,Windows 上几乎所有标准库功能都直接基于最低层级的稳定 syscall API 实现。仍会调用 Windows DLL 的 extern 函数仅剩: + +- `kernel32.CreateProcessW` +- `crypt32` 一组:`CertOpenStore` / `CertCloseStore` / `CertEnumCertificatesInStore` / `CertFreeCertificateContext` / `CertAddEncodedCertificateToStore` / `CertOpenSystemStoreW` / `CertGetCertificateChain` / `CertFreeCertificateChain` / `CertVerifyCertificateChainPolicy` + +短期内不打算继续迁移这两组。如果你需要在 XP 这类老 Windows 上运行,或者更倾向走高层 DLL,建议作为社区项目实现一个不依赖 NtDll 的第三方 `Io`。 + +### 各目标上的栈回溯能力进一步扩大 + +`0.16.0` 在“几乎所有真正在用的目标”上都补齐了崩溃和 `DebugAllocator` 场景下的栈追踪能力。Windows 下打印 stack trace 时,inline caller 会从 PDB debug info 中被解析;如果 debug info 模糊,所有候选 caller 都会被打印出来。error return trace 现在在所有平台上都包含 inline caller。 + +这一切都是“为做游戏开发改善 Zig 体验”这条更大计划的一部分,对应用代码通常透明,但意味着遇到崩溃时你能拿到更多有用信息。 + ### Fuzz 测试接口改成 `std.testing.Smith` 如果你的项目用到了 fuzz 测试,这也是一个直接的 breaking change。 @@ -709,6 +1594,10 @@ zig build --error-style minimal 如果你平时配合 `--watch` 或增量编译工作流,`verbose_clear` / `minimal_clear` 这两种 error style 会比较顺手。 +`--error-style` 与 `--multiline-errors` 都额外支持通过环境变量 `ZIG_BUILD_ERROR_STYLE` / `ZIG_BUILD_MULTILINE_ERRORS` 设默认值,便于你在 shell 配置里一次性写好。`--multiline-errors` 可选值为 `indent`(新默认)、`newline`、`none`,分别对应“后续行缩进对齐第一行”“在第一行前补一个换行让所有行从首列起头”“原样输出不做处理”。 + +另外,Zig 不再读取 `ZIG_BTRFS_WORKAROUND` 这个旧环境变量——上游 Linux 那边的 bug 早已修复([#17095](https://github.com/ziglang/zig/issues/17095))。 + ### 临时文件 API 被重构 这次构建系统还清理了旧的临时目录 API: From 5df4e655ad9e041cd567b0eaa0d4afe10a54640c Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Wed, 6 May 2026 00:41:14 +0800 Subject: [PATCH 3/9] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=20release=20?= =?UTF-8?q?=E6=8C=87=E5=90=91=E4=B8=8B=E4=B8=80=E4=B8=AA=E8=AF=BE=E7=A8=8B?= =?UTF-8?q?=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 推动课程进入新阶段,便于后续内容发布与迭代 --- course/code/release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/course/code/release b/course/code/release index 99b5cf48..c3e11da9 120000 --- a/course/code/release +++ b/course/code/release @@ -1 +1 @@ -./15 \ No newline at end of file +./16 \ No newline at end of file From 6a43f7d6fec32bc13ff08f219d6e2b1dc9bb972b Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Thu, 7 May 2026 21:45:53 +0800 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=E5=8D=87=E7=BA=A7=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=E4=B8=8E=E6=96=87=E6=A1=A3=E4=BB=A5=E9=80=82=E9=85=8D?= =?UTF-8?q?=20Zig=200.16=20=E7=9A=84=E6=96=B0=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整所有涉及 libc 链接和构建脚本的示例,统一使用 exe.root_module.linkSystemLibrary("c", .{}) - 大幅重构 build.zig 示例,兼容 Zig 0.16 引入的 Io 模块与新目录 API - 更新与内存分配相关的文档内容,注明 DebugAllocator 等新接口用法 - 修正线程安全分配器示例,补充 Zig 0.16 移除 ThreadSafeAllocator 的说明 - 优化 C 交互、指针运算、类型转换等文档的说明和措辞以反映新版本变更 - 新增 package_management_importer 的示例和代码,展示包管理用法 本次变更适应 Zig 0.16 新特性,简化了学习曲线,也减少了潜在的 API 误用。 --- build/0.16.zig | 50 ++- course/advanced/atomic.md | 2 +- course/advanced/interact-with-c.md | 2 +- course/advanced/memory_manage.md | 6 +- course/advanced/result-location.md | 4 +- course/advanced/type_cast.md | 2 +- course/basic/advanced_type/pointer.md | 2 +- .../code/16/build_system/basic/src/main.zig | 7 +- course/code/16/build_system/build.zig | 81 ++-- course/code/16/build_system/cli/src/main.zig | 7 +- course/code/16/build_system/lib/build.zig | 2 +- course/code/16/build_system/lib/src/main.zig | 7 +- course/code/16/build_system/step/src/main.zig | 7 +- .../code/16/build_system/system_lib/build.zig | 6 +- course/code/16/build_system/test/src/main.zig | 7 +- .../code/16/build_system/tinytetris/build.zig | 10 +- course/code/16/hello_world.zig | 26 +- .../code/16/import_dependency_build/build.zig | 81 ++-- course/code/16/import_vcpkg/build.zig | 8 +- course/code/16/memory_manager.zig | 19 +- .../package_management_importer/src/main.zig | 5 +- course/code/16/result-location.zig | 390 ++++++++++++++++++ course/code/16/struct.zig | 2 +- course/engineering/build-system.md | 6 +- course/engineering/package_management.md | 9 +- course/hello-world.md | 16 +- 26 files changed, 569 insertions(+), 195 deletions(-) create mode 100644 course/code/16/result-location.zig diff --git a/build/0.16.zig b/build/0.16.zig index 9dc29152..f672acc7 100644 --- a/build/0.16.zig +++ b/build/0.16.zig @@ -1,7 +1,5 @@ const std = @import("std"); const Build = std.Build; -const ChildProcess = std.process.Child; - const log = std.log.scoped(.For_0_16_0); const version = "16"; @@ -13,25 +11,31 @@ pub fn build(b: *Build) void { // get target and optimize const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const io = b.graph.io; var lazy_path = b.path(relative_path); const full_path = lazy_path.getPath(b); // open dir - var dir = std.fs.openDirAbsolute(full_path, .{ .iterate = true }) catch |err| { - log.err("open 15 path failed, err is {}", .{err}); + var dir = std.Io.Dir.openDirAbsolute(io, full_path, .{ .iterate = true }) catch |err| { + log.err("open 16 path failed, err is {}", .{err}); std.process.exit(1); }; - defer dir.close(); + defer dir.close(io); // make a iterate for release ath var iterate = dir.iterate(); - while (iterate.next()) |val| { - if (val) |entry| { + while (iterate.next(io) catch |err| { + log.err("iterate examples_path failed, err is {}", .{err}); + std.process.exit(1); + }) |entry| { // get the entry name, entry can be file or directory - const output_name = std.mem.trimRight(u8, entry.name, ".zig"); + const output_name = if (std.mem.endsWith(u8, entry.name, ".zig")) + entry.name[0 .. entry.name.len - ".zig".len] + else + entry.name; if (entry.kind == .file) { // connect path const path = std.fs.path.join(b.allocator, &[_][]const u8{ relative_path, entry.name }) catch |err| { @@ -48,11 +52,11 @@ pub fn build(b: *Build) void { .optimize = optimize, }), }); - exe.linkLibC(); + exe.root_module.linkSystemLibrary("c", .{}); if (exe.root_module.resolved_target.?.result.os.tag == .windows and std.mem.eql(u8, "echo_tcp_server.zig", entry.name)) { std.log.info("link ws2_32 for {s}", .{entry.name}); - exe.linkSystemLibrary("ws2_32"); + exe.root_module.linkSystemLibrary("ws2_32", .{}); } // add to default install b.installArtifact(exe); @@ -75,8 +79,6 @@ pub fn build(b: *Build) void { } else if (entry.kind == .directory) { // build child process - var child = ChildProcess.init(&args, b.allocator); - // build cwd const cwd = std.fs.path.join(b.allocator, &[_][]const u8{ full_path, @@ -87,25 +89,19 @@ pub fn build(b: *Build) void { }; // open entry dir - const entry_dir = std.fs.openDirAbsolute(cwd, .{}) catch unreachable; - entry_dir.access("build.zig", .{}) catch { + const entry_dir = std.Io.Dir.openDirAbsolute(io, cwd, .{}) catch unreachable; + defer entry_dir.close(io); + + entry_dir.access(io, "build.zig", .{}) catch { log.err("not found build.zig in path {s}", .{cwd}); std.process.exit(1); }; - // set child cwd - // this api maybe changed in the future - child.cwd = cwd; - - // spawn and wait child process - _ = child.spawnAndWait() catch unreachable; + var child = std.process.spawn(io, .{ + .argv = &args, + .cwd = .{ .path = cwd }, + }) catch unreachable; + _ = child.wait(io) catch unreachable; } - } else { - // Stop endless loop - break; - } - } else |err| { - log.err("iterate examples_path failed, err is {}", .{err}); - std.process.exit(1); } } diff --git a/course/advanced/atomic.md b/course/advanced/atomic.md index 2a88ec70..64ebb469 100644 --- a/course/advanced/atomic.md +++ b/course/advanced/atomic.md @@ -148,5 +148,5 @@ outline: deep - **Mutex**:适合等待时间不确定或较长的场景,会让出 CPU 给其他线程 ::: warning ⚠️ 警告 -不当使用自旋等待会导致 CPU 资源浪费。如果等待时间较长或不确定,应使用 `std.Thread.Mutex` 等同步原语。 +不当使用自旋等待会导致 CPU 资源浪费。如果等待时间较长或不确定,应使用 `std.Io.Mutex` 这类会让出 CPU 的同步原语。 ::: diff --git a/course/advanced/interact-with-c.md b/course/advanced/interact-with-c.md index 9a2352ad..b14fafb9 100644 --- a/course/advanced/interact-with-c.md +++ b/course/advanced/interact-with-c.md @@ -44,7 +44,7 @@ C 语言共享类型通常是通过引入头文件实现,这点在 zig 中可 ::: info 🅿️ 提示 -注意:为了构建这个,我们需要引入 `libc`,可以在 `build.zig` 中添加 `exe.linkLibC` 函数,`exe` 是默认的构建变量。 +注意:为了构建这个,我们需要引入 `libc`。在 Zig 0.16 的构建脚本中,可以让对应模块链接 C 标准库,例如 `exe.root_module.linkSystemLibrary("c", .{})`。 或者我们可以手动执行构建:`zig build-exe source.zig -lc` diff --git a/course/advanced/memory_manage.md b/course/advanced/memory_manage.md index 50549b2a..a0fe7e98 100644 --- a/course/advanced/memory_manage.md +++ b/course/advanced/memory_manage.md @@ -42,7 +42,7 @@ outline: deep 这是一个用于调试的分配器,现阶段适用于在调试模式下使用该分配器,它的性能并不高! -这个分配器的目的不是为了性能,而是为了安全,它支持线程安全,安全检查,检查是否存在泄露等特性,这些特性均可手动配置是否开启。 +这个分配器的目的不是为了性能,而是为了安全。默认配置下它支持线程安全、安全检查、泄漏检测等能力,并且这些特性都可以按需配置。 <<<@/code/release/memory_manager.zig#DebugAllocator @@ -66,7 +66,7 @@ outline: deep ## `FixedBufferAllocator` -这个分配器是固定大小的内存缓冲区,无法扩容,常常在你需要缓冲某些东西时使用,注意默认情况下它不是线程安全的,但是存在着变体 [`ThreadSafeAllocator`](https://ziglang.org/documentation/master/std/#std.heap.ThreadSafeAllocator),使用 `ThreadSafeAllocator` 包裹一下它即可。 +这个分配器是固定大小的内存缓冲区,无法扩容,常常在你需要缓冲某些东西时使用。注意默认情况下它不是线程安全的;而在 Zig 0.16 中,`ThreadSafeAllocator` 已被移除。如果你需要跨线程共享同一个 `FixedBufferAllocator`,应当像示例那样在外层自行加锁,或者直接改用更适合并发场景的 `SmpAllocator`。 ::: code-group @@ -84,7 +84,7 @@ outline: deep ## `c_allocator` -这是纯粹的 C 的 `malloc`,它会直接尝试调用 C 库的内存分配,使用它需要在 `build.zig` 中添加上 `linkLibC` 功能: +这是纯粹的 C 的 `malloc`,它会直接尝试调用 C 库的内存分配。使用它时,需要在 `build.zig` 中让对应模块链接 libc,例如 `exe.root_module.linkSystemLibrary("c", .{})`: <<<@/code/release/memory_manager.zig#c_allocator diff --git a/course/advanced/result-location.md b/course/advanced/result-location.md index 80d28518..4c13a1c7 100644 --- a/course/advanced/result-location.md +++ b/course/advanced/result-location.md @@ -106,9 +106,9 @@ outline: deep <<<@/code/release/result-location.zig#stdlib_arraylist -### GeneralPurposeAllocator +### DebugAllocator -<<<@/code/release/result-location.zig#stdlib_gpa +<<<@/code/release/result-location.zig#stdlib_debug_allocator ## 字段和声明不可重名 diff --git a/course/advanced/type_cast.md b/course/advanced/type_cast.md index ed7465a4..64c25561 100644 --- a/course/advanced/type_cast.md +++ b/course/advanced/type_cast.md @@ -116,7 +116,7 @@ undefined 是一个神奇的值,它可以赋值给所有类型,代表这个 - [`@floatFromInt`](https://ziglang.org/documentation/master/#floatFromInt) 将整数显式强制转换为浮点数 - [`@intCast`](https://ziglang.org/documentation/master/#intCast) 在不同的整数类型中显式强制转换 - [`@intFromBool`](https://ziglang.org/documentation/master/#intFromBool) 将 `true` 转换为 `1`,`false` 转换为 `0` -- [`@intFromEnum`](https://ziglang.org/documentation/master/#intFromEnum) 根据整数值获取对应的联合标记或者枚举值 +- [`@intFromEnum`](https://ziglang.org/documentation/master/#intFromEnum) 获取枚举值或联合标记对应的整数值 - [`@intFromError`](https://ziglang.org/documentation/master/#intFromError) 获取对应错误的整数值 - [`@intFromFloat`](https://ziglang.org/documentation/master/#intFromFloat) 获取浮点数的整数部分 - [`@intFromPtr`](https://ziglang.org/documentation/master/#intFromPtr) 获取指针指向的地址(整数 `usize`),这在嵌入式开发和内核开发时很常用 diff --git a/course/basic/advanced_type/pointer.md b/course/basic/advanced_type/pointer.md index 65ef09fd..ae3c02d7 100644 --- a/course/basic/advanced_type/pointer.md +++ b/course/basic/advanced_type/pointer.md @@ -112,7 +112,7 @@ Zig 支持指针的加减运算,但建议在进行运算前,将指针转换 <<<@/code/release/pointer.zig#st_pointer -以上代码编译需要额外链接 libc,你只需要在你的 `build.zig` 中添加 `exe.linkLibC();` 即可。 +以上代码编译需要额外链接 `libc`。在 Zig 0.16 的构建脚本中,可以让对应模块链接 C 标准库,例如 `exe.root_module.linkSystemLibrary("c", .{})`。 ::: diff --git a/course/code/16/build_system/basic/src/main.zig b/course/code/16/build_system/basic/src/main.zig index 96d9a362..bf45938f 100644 --- a/course/code/16/build_system/basic/src/main.zig +++ b/course/code/16/build_system/basic/src/main.zig @@ -1,14 +1,15 @@ const std = @import("std"); -pub fn main() !void { - // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) +pub fn main(init: std.process.Init) !void { + const io = init.io; + // `std.debug.print` 会输出到标准错误。 std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); // stdout is for the actual output of your application, for example if you // are implementing gzip, then only the compressed bytes should be sent to // stdout, not any debugging messages. var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; try stdout.print("Run `zig build test` to run the tests.\n", .{}); diff --git a/course/code/16/build_system/build.zig b/course/code/16/build_system/build.zig index 5909232b..2cc7cd76 100644 --- a/course/code/16/build_system/build.zig +++ b/course/code/16/build_system/build.zig @@ -1,10 +1,9 @@ const std = @import("std"); -const ChildProcess = std.process.Child; - const args = [_][]const u8{ "zig", "build" }; pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); + const io = b.graph.io; // #region crossTarget // 构建一个target const target_query = std.Target.Query{ @@ -36,57 +35,51 @@ pub fn build(b: *std.Build) !void { b.installArtifact(exe); - const full_path = try std.process.getCwdAlloc(b.allocator); + const full_path = try std.process.currentPathAlloc(io, b.allocator); + defer b.allocator.free(full_path); - var dir = std.fs.openDirAbsolute(full_path, .{ .iterate = true }) catch |err| { + var dir = std.Io.Dir.openDirAbsolute(io, full_path, .{ .iterate = true }) catch |err| { std.log.err("open path failed {s}, err is {}", .{ full_path, err }); std.process.exit(1); }; - defer dir.close(); + defer dir.close(io); var iterate = dir.iterate(); - while (iterate.next()) |val| { - if (val) |entry| { - // get the entry name, entry can be file or directory - const name = entry.name; - if (entry.kind == .directory) { - if (eqlu8(name, ".zig-cache") or eqlu8(name, "zig-out") or eqlu8(name, "zig-cache")) - continue; - - // build child process - var child = ChildProcess.init(&args, b.allocator); - - // build cwd - const cwd = std.fs.path.join(b.allocator, &[_][]const u8{ - full_path, - name, - }) catch |err| { - std.log.err("fmt path failed, err is {}", .{err}); - std.process.exit(1); - }; - - // open entry dir - const entry_dir = std.fs.openDirAbsolute(cwd, .{}) catch unreachable; - entry_dir.access("build.zig", .{}) catch { - std.log.err("not found build.zig in path {s}", .{cwd}); - std.process.exit(1); - }; - - // set child cwd - // this api maybe changed in the future - child.cwd = cwd; - - // spawn and wait child process - _ = child.spawnAndWait() catch unreachable; - } - } else { - // Stop endless loop - break; - } - } else |err| { + while (iterate.next(io) catch |err| { std.log.err("iterate examples_path failed, err is {}", .{err}); std.process.exit(1); + }) |entry| { + // get the entry name, entry can be file or directory + const name = entry.name; + if (entry.kind == .directory) { + if (eqlu8(name, ".zig-cache") or eqlu8(name, "zig-out") or eqlu8(name, "zig-cache")) + continue; + + // build cwd + const cwd = std.fs.path.join(b.allocator, &[_][]const u8{ + full_path, + name, + }) catch |err| { + std.log.err("fmt path failed, err is {}", .{err}); + std.process.exit(1); + }; + + // open entry dir + const entry_dir = std.Io.Dir.openDirAbsolute(io, cwd, .{}) catch unreachable; + defer entry_dir.close(io); + + entry_dir.access(io, "build.zig", .{}) catch { + std.log.err("not found build.zig in path {s}", .{cwd}); + std.process.exit(1); + }; + + var child = std.process.spawn(io, .{ + .argv = &args, + .cwd = .{ .path = cwd }, + }) catch unreachable; + _ = child.wait(io) catch unreachable; + } } } diff --git a/course/code/16/build_system/cli/src/main.zig b/course/code/16/build_system/cli/src/main.zig index 2e81a719..947ed515 100644 --- a/course/code/16/build_system/cli/src/main.zig +++ b/course/code/16/build_system/cli/src/main.zig @@ -1,14 +1,15 @@ const std = @import("std"); -pub fn main() !void { - // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) +pub fn main(init: std.process.Init) !void { + const io = init.io; + // `std.debug.print` 会输出到标准错误。 std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); // stdout is for the actual output of your application, for example if you // are implementing gzip, then only the compressed bytes should be sent to // stdout, not any debugging messages. var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; try stdout.print("Run `zig build test` to run the tests.\n", .{}); diff --git a/course/code/16/build_system/lib/build.zig b/course/code/16/build_system/lib/build.zig index e5edc582..2d0b4020 100644 --- a/course/code/16/build_system/lib/build.zig +++ b/course/code/16/build_system/lib/build.zig @@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void { }), }); - exe.linkLibrary(lib); + exe.root_module.linkLibrary(lib); b.installArtifact(exe); } diff --git a/course/code/16/build_system/lib/src/main.zig b/course/code/16/build_system/lib/src/main.zig index 734464bc..34185e55 100644 --- a/course/code/16/build_system/lib/src/main.zig +++ b/course/code/16/build_system/lib/src/main.zig @@ -1,14 +1,15 @@ const std = @import("std"); -pub fn main() !void { - // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) +pub fn main(init: std.process.Init) !void { + const io = init.io; + // `std.debug.print` 会输出到标准错误。 std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); // stdout is for the actual output of your application, for example if you // are implementing gzip, then only the compressed bytes should be sent to // stdout, not any debugging messages. var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; try stdout.print("Run `zig build test` to run the tests.\n", .{}); diff --git a/course/code/16/build_system/step/src/main.zig b/course/code/16/build_system/step/src/main.zig index 734464bc..34185e55 100644 --- a/course/code/16/build_system/step/src/main.zig +++ b/course/code/16/build_system/step/src/main.zig @@ -1,14 +1,15 @@ const std = @import("std"); -pub fn main() !void { - // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) +pub fn main(init: std.process.Init) !void { + const io = init.io; + // `std.debug.print` 会输出到标准错误。 std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); // stdout is for the actual output of your application, for example if you // are implementing gzip, then only the compressed bytes should be sent to // stdout, not any debugging messages. var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; try stdout.print("Run `zig build test` to run the tests.\n", .{}); diff --git a/course/code/16/build_system/system_lib/build.zig b/course/code/16/build_system/system_lib/build.zig index bf986458..55ef0097 100644 --- a/course/code/16/build_system/system_lib/build.zig +++ b/course/code/16/build_system/system_lib/build.zig @@ -20,13 +20,13 @@ pub fn build(b: *std.Build) void { if (target.result.os.tag == .windows) // 连接到系统的 ole32 - exe.linkSystemLibrary("ole32") + exe.root_module.linkSystemLibrary("ole32", .{}) else // 链接到系统的 libz - exe.linkSystemLibrary("z"); + exe.root_module.linkSystemLibrary("z", .{}); // 链接到 libc - exe.linkLibC(); + exe.root_module.linkSystemLibrary("c", .{}); b.installArtifact(exe); } diff --git a/course/code/16/build_system/test/src/main.zig b/course/code/16/build_system/test/src/main.zig index 96d9a362..bf45938f 100644 --- a/course/code/16/build_system/test/src/main.zig +++ b/course/code/16/build_system/test/src/main.zig @@ -1,14 +1,15 @@ const std = @import("std"); -pub fn main() !void { - // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) +pub fn main(init: std.process.Init) !void { + const io = init.io; + // `std.debug.print` 会输出到标准错误。 std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); // stdout is for the actual output of your application, for example if you // are implementing gzip, then only the compressed bytes should be sent to // stdout, not any debugging messages. var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; try stdout.print("Run `zig build test` to run the tests.\n", .{}); diff --git a/course/code/16/build_system/tinytetris/build.zig b/course/code/16/build_system/tinytetris/build.zig index ba101e52..dd977698 100644 --- a/course/code/16/build_system/tinytetris/build.zig +++ b/course/code/16/build_system/tinytetris/build.zig @@ -26,17 +26,17 @@ pub fn build(b: *std.Build) void { // 源代码路径(相对于build.zig) // 传递的 flags // 多个 C 源代码文件可以使用 addCSourceFiles - exe.addCSourceFile(.{ + exe.root_module.addCSourceFile(.{ .file = b.path("src/main.cc"), .flags = &.{}, }); - // 链接C++ 标准库 - // 同理对于 C 标准库可以使用 linkLibC - exe.linkLibCpp(); + // 链接 C++ 标准库 + // 同理对于 C 标准库可以使用 `exe.root_module.linkSystemLibrary("c", .{})` + exe.root_module.linkSystemLibrary("c++", .{}); // 链接系统库 ncurses - exe.linkSystemLibrary("ncurses"); + exe.root_module.linkSystemLibrary("ncurses", .{}); // 添加到顶级 install step 中作为依赖 b.installArtifact(exe); diff --git a/course/code/16/hello_world.zig b/course/code/16/hello_world.zig index 9c1a521b..35a6ec9f 100644 --- a/course/code/16/hello_world.zig +++ b/course/code/16/hello_world.zig @@ -1,12 +1,13 @@ -pub fn main() !void { +pub fn main(init: std.process.Init) !void { try One.main(); - try Two.main(); - try Three.main(); + try Two.main(init.io); + try Three.main(init.io); } +const std = @import("std"); + const One = struct { // #region one - const std = @import("std"); pub fn main() !void { std.debug.print("Hello, World!\n", .{}); } @@ -15,14 +16,13 @@ const One = struct { const Two = struct { // #region two - const std = @import("std"); - pub fn main() !void { + pub fn main(io: std.Io) !void { var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; var stderr_buffer: [1024]u8 = undefined; - var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer); + var stderr_writer = std.Io.File.stderr().writer(io, &stderr_buffer); const stderr = &stderr_writer.interface; try stdout.print("Hello {s}!\n", .{"out"}); @@ -35,27 +35,23 @@ const Two = struct { const Three = struct { // #region three - const std = @import("std"); - pub fn main() !void { + pub fn main(io: std.Io) !void { // 定义两个缓冲区 var stdout_buffer: [1024]u8 = undefined; // [!code focus] var stderr_buffer: [1024]u8 = undefined; // [!code focus] // 获取writer句柄// [!code focus] - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; // 获取writer句柄// [!code focus] - var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer); + var stderr_writer = std.Io.File.stderr().writer(io, &stderr_buffer); const stderr = &stderr_writer.interface; // 通过句柄写入buffer// [!code focus] try stdout.print("Hello {s}!\n", .{"out"}); // [!code focus] try stderr.print("Hello {s}!\n", .{"err"}); // [!code focus] - try stdout.flush(); - try stderr.flush(); - // 尝试刷新buffer// [!code focus] try stdout.flush(); // [!code focus] try stderr.flush(); // [!code focus] diff --git a/course/code/16/import_dependency_build/build.zig b/course/code/16/import_dependency_build/build.zig index 2e1e3913..c646ed7f 100644 --- a/course/code/16/import_dependency_build/build.zig +++ b/course/code/16/import_dependency_build/build.zig @@ -1,60 +1,53 @@ const std = @import("std"); -const ChildProcess = std.process.Child; - const args = [_][]const u8{ "zig", "build" }; pub fn build(b: *std.Build) !void { - const full_path = try std.process.getCwdAlloc(b.allocator); + const io = b.graph.io; + const full_path = try std.process.currentPathAlloc(io, b.allocator); + defer b.allocator.free(full_path); - var dir = std.fs.openDirAbsolute(full_path, .{ .iterate = true }) catch |err| { + var dir = std.Io.Dir.openDirAbsolute(io, full_path, .{ .iterate = true }) catch |err| { std.log.err("open path failed {s}, err is {}", .{ full_path, err }); std.process.exit(1); }; - defer dir.close(); + defer dir.close(io); var iterate = dir.iterate(); - while (iterate.next()) |val| { - if (val) |entry| { - // get the entry name, entry can be file or directory - const name = entry.name; - if (entry.kind == .directory) { - if (eqlu8(name, ".zig-cache") or eqlu8(name, "zig-out") or eqlu8(name, "zig-cache")) - continue; - - // build child process - var child = ChildProcess.init(&args, b.allocator); - - // build cwd - const cwd = std.fs.path.join(b.allocator, &[_][]const u8{ - full_path, - name, - }) catch |err| { - std.log.err("fmt path failed, err is {}", .{err}); - std.process.exit(1); - }; - - // open entry dir - const entry_dir = std.fs.openDirAbsolute(cwd, .{}) catch unreachable; - entry_dir.access("build.zig", .{}) catch { - std.log.err("not found build.zig in path {s}", .{cwd}); - std.process.exit(1); - }; - - // set child cwd - // this api maybe changed in the future - child.cwd = cwd; - - // spawn and wait child process - _ = child.spawnAndWait() catch unreachable; - } - } else { - // Stop endless loop - break; - } - } else |err| { + while (iterate.next(io) catch |err| { std.log.err("iterate examples_path failed, err is {}", .{err}); std.process.exit(1); + }) |entry| { + // get the entry name, entry can be file or directory + const name = entry.name; + if (entry.kind == .directory) { + if (eqlu8(name, ".zig-cache") or eqlu8(name, "zig-out") or eqlu8(name, "zig-cache")) + continue; + + // build cwd + const cwd = std.fs.path.join(b.allocator, &[_][]const u8{ + full_path, + name, + }) catch |err| { + std.log.err("fmt path failed, err is {}", .{err}); + std.process.exit(1); + }; + + // open entry dir + const entry_dir = std.Io.Dir.openDirAbsolute(io, cwd, .{}) catch unreachable; + defer entry_dir.close(io); + + entry_dir.access(io, "build.zig", .{}) catch { + std.log.err("not found build.zig in path {s}", .{cwd}); + std.process.exit(1); + }; + + var child = std.process.spawn(io, .{ + .argv = &args, + .cwd = .{ .path = cwd }, + }) catch unreachable; + _ = child.wait(io) catch unreachable; + } } } diff --git a/course/code/16/import_vcpkg/build.zig b/course/code/16/import_vcpkg/build.zig index bb4a649c..7475753a 100644 --- a/course/code/16/import_vcpkg/build.zig +++ b/course/code/16/import_vcpkg/build.zig @@ -16,13 +16,13 @@ const Build = struct { }); // #region c_import // 增加 include 搜索目录 - exe.addIncludePath(.{ .cwd_relative = "D:\\vcpkg\\installed\\windows-x64\\include" }); + exe.root_module.addIncludePath(.{ .cwd_relative = "D:\\vcpkg\\installed\\windows-x64\\include" }); // 增加 lib 搜索目录 - exe.addLibraryPath(.{ .cwd_relative = "D:\\vcpkg\\installed\\windows-x64\\lib" }); + exe.root_module.addLibraryPath(.{ .cwd_relative = "D:\\vcpkg\\installed\\windows-x64\\lib" }); // 链接标准c库 - exe.linkLibC(); + exe.root_module.linkSystemLibrary("c", .{}); // 链接第三方库gsl - exe.linkSystemLibrary("gsl"); + exe.root_module.linkSystemLibrary("gsl", .{}); // #endregion c_import b.installArtifact(exe); diff --git a/course/code/16/memory_manager.zig b/course/code/16/memory_manager.zig index eec3958f..52ae7f16 100644 --- a/course/code/16/memory_manager.zig +++ b/course/code/16/memory_manager.zig @@ -86,16 +86,19 @@ const ThreadSafeFixedBufferAllocator = struct { // 获取内存allocator const allocator = fba.allocator(); - // 使用 ThreadSafeAllocator 包裹, 你需要设置使用的内存分配器,还可以配置使用的mutex - var thread_safe_fba = std.heap.ThreadSafeAllocator{ .child_allocator = allocator }; + // Zig 0.16 移除了 ThreadSafeAllocator。 + // 如果需要在线程间共享 FixedBufferAllocator,需要自行保护临界区。 + var mutex: std.atomic.Mutex = .unlocked; - // 获取线程安全的内存allocator - const thread_safe_allocator = thread_safe_fba.allocator(); + while (!mutex.tryLock()) { + std.atomic.spinLoopHint(); + } + defer mutex.unlock(); // 申请内存 - const memory = try thread_safe_allocator.alloc(u8, 100); + const memory = try allocator.alloc(u8, 100); // 释放内存 - defer thread_safe_allocator.free(memory); + defer allocator.free(memory); } // #endregion ThreadSafeFixedBufferAllocator }; @@ -129,11 +132,11 @@ const ArenaAllocator = struct { pub fn main() !void { // 使用模型,一定要是变量,不能是常量 - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa = std.heap.DebugAllocator(.{}){}; // 拿到一个allocator const allocator = gpa.allocator(); - // defer 用于执行general_purpose_allocator善后工作 + // defer 用于执行 debug allocator 善后工作 defer { const deinit_status = gpa.deinit(); diff --git a/course/code/16/package_management_importer/src/main.zig b/course/code/16/package_management_importer/src/main.zig index ef0882e1..5c0df21e 100644 --- a/course/code/16/package_management_importer/src/main.zig +++ b/course/code/16/package_management_importer/src/main.zig @@ -2,9 +2,10 @@ const std = @import("std"); const pe = @import("path_exporter"); const te = @import("tarball_exporter"); -pub fn main() !void { +pub fn main(init: std.process.Init) !void { + const io = init.io; var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; const str2: te.Str = .{ .str = "2" }; diff --git a/course/code/16/result-location.zig b/course/code/16/result-location.zig new file mode 100644 index 00000000..638cce6f --- /dev/null +++ b/course/code/16/result-location.zig @@ -0,0 +1,390 @@ +const std = @import("std"); + +pub fn main() !void { + BasicInference.main(); + ResultTypeVariable.main(); + ResultTypeReturn.main(); + ResultTypeParam.main(); + ResultTypeFieldDefault.main(); + ResultLocationNested.main(); + DeclLiteralBasic.main(); + DeclLiteralFieldDefault.main(); + DeclLiteralFunction.main(); + // 以下示例需要 allocator,仅在测试中运行 + // try DeclLiteralErrorUnion.main(); + // try StdLibArrayList.main(); + try StdLibDebugAllocator.main(); +} + +const BasicInference = struct { + // #region basic_inference + const Point = struct { + x: i32, + y: i32, + }; + + pub fn main() void { + // 编译器从变量类型推断出 .{} 的具体类型 + const pt: Point = .{ .x = 10, .y = 20 }; + + // 等价于 + const pt2: Point = Point{ .x = 10, .y = 20 }; + + std.debug.print("pt: ({}, {}), pt2: ({}, {})\n", .{ pt.x, pt.y, pt2.x, pt2.y }); + } + // #endregion basic_inference +}; + +const ResultTypeVariable = struct { + // #region result_type_variable + const Color = struct { + r: u8, + g: u8, + b: u8, + }; + + pub fn main() void { + // 结果类型是 Color + const red: Color = .{ .r = 255, .g = 0, .b = 0 }; + std.debug.print("red: ({}, {}, {})\n", .{ red.r, red.g, red.b }); + } + // #endregion result_type_variable +}; + +const ResultTypeReturn = struct { + // #region result_type_return + const Vec2 = struct { + x: f32, + y: f32, + }; + + fn origin() Vec2 { + // 结果类型是 Vec2 + return .{ .x = 0, .y = 0 }; + } + + pub fn main() void { + const o = origin(); + std.debug.print("origin: ({d}, {d})\n", .{ o.x, o.y }); + } + // #endregion result_type_return +}; + +const ResultTypeParam = struct { + // #region result_type_param + const Size = struct { + width: u32, + height: u32, + }; + + fn calculateArea(size: Size) u64 { + return @as(u64, size.width) * size.height; + } + + pub fn main() void { + // 调用时,.{} 的结果类型是 Size + const area = calculateArea(.{ .width = 100, .height = 50 }); + std.debug.print("area: {}\n", .{area}); + } + // #endregion result_type_param +}; + +const ResultTypeFieldDefault = struct { + // #region result_type_field_default + const Config = struct { + timeout: u32 = 30, + retries: u8 = 3, + }; + + const Wrapper = struct { + // 字段类型是 Config,所以 .{} 的结果类型是 Config + config: Config = .{}, + }; + + pub fn main() void { + const w: Wrapper = .{}; + std.debug.print("timeout: {}, retries: {}\n", .{ w.config.timeout, w.config.retries }); + } + // #endregion result_type_field_default +}; + +const ResultLocationNested = struct { + // #region result_location_nested + const Inner = struct { + value: i32, + }; + + const Outer = struct { + inner: Inner, + name: []const u8, + }; + + pub fn main() void { + // 结果位置 Outer 传播到 inner 字段,使其结果类型为 Inner + const obj: Outer = .{ + .inner = .{ .value = 42 }, // 这里 .{} 的结果类型是 Inner + .name = "example", + }; + std.debug.print("inner.value: {}, name: {s}\n", .{ obj.inner.value, obj.name }); + } + // #endregion result_location_nested +}; + +const DeclLiteralBasic = struct { + // #region decl_literal_basic + const S = struct { + x: u32, + + // 类型内的常量声明 + const default: S = .{ .x = 123 }; + }; + + pub fn main() void { + // .default 会被解析为 S.default + const val: S = .default; + std.debug.print("val.x: {}\n", .{val.x}); + } + + test "decl literal" { + const val: S = .default; + try std.testing.expectEqual(123, val.x); + } + // #endregion decl_literal_basic +}; + +const DeclLiteralFieldDefault = struct { + // #region decl_literal_field_default + const Settings = struct { + x: u32, + y: u32, + + const default: Settings = .{ .x = 1, .y = 2 }; + const high_performance: Settings = .{ .x = 100, .y = 200 }; + }; + + const Application = struct { + // 使用声明字面量设置默认值 + settings: Settings = .default, + }; + + pub fn main() void { + const app1: Application = .{}; + std.debug.print("app1.settings: ({}, {})\n", .{ app1.settings.x, app1.settings.y }); + + // 也可以覆盖为其他预定义值 + const app2: Application = .{ .settings = .high_performance }; + std.debug.print("app2.settings: ({}, {})\n", .{ app2.settings.x, app2.settings.y }); + } + + test "decl literal in field default" { + const app1: Application = .{}; + try std.testing.expectEqual(1, app1.settings.x); + + const app2: Application = .{ .settings = .high_performance }; + try std.testing.expectEqual(100, app2.settings.x); + } + // #endregion decl_literal_field_default +}; + +const DeclLiteralFunction = struct { + // #region decl_literal_function + const Point = struct { + x: i32, + y: i32, + + fn init(val: i32) Point { + return .{ .x = val, .y = val }; + } + + fn offset(val: i32, dx: i32, dy: i32) Point { + return .{ .x = val + dx, .y = val + dy }; + } + }; + + pub fn main() void { + // .init(5) 等价于 Point.init(5) + const p1: Point = .init(5); + std.debug.print("p1: ({}, {})\n", .{ p1.x, p1.y }); + + const p2: Point = .offset(0, 10, 20); + std.debug.print("p2: ({}, {})\n", .{ p2.x, p2.y }); + } + + test "call function via decl literal" { + const p1: Point = .init(5); + try std.testing.expectEqual(5, p1.x); + try std.testing.expectEqual(5, p1.y); + + const p2: Point = .offset(0, 10, 20); + try std.testing.expectEqual(10, p2.x); + try std.testing.expectEqual(20, p2.y); + } + // #endregion decl_literal_function +}; + +const DeclLiteralErrorUnion = struct { + // #region decl_literal_error_union + const Buffer = struct { + data: std.ArrayListUnmanaged(u32), + + fn initCapacity(allocator: std.mem.Allocator, capacity: usize) !Buffer { + return .{ .data = try .initCapacity(allocator, capacity) }; + } + }; + + test "decl literal with error union" { + var buf: Buffer = try .initCapacity(std.testing.allocator, 10); + defer buf.data.deinit(std.testing.allocator); + + buf.data.appendAssumeCapacity(42); + try std.testing.expectEqual(42, buf.data.items[0]); + } + // #endregion decl_literal_error_union +}; + +const FaultyDefaultValues = struct { + // #region faulty_default_problem + /// `ptr` 指向 `[len]u32` + pub const BufferA = extern struct { + ptr: ?[*]u32 = null, + len: usize = 0, + }; + + // 看起来是空 buffer + var empty_buf: BufferA = .{}; + + // 但用户可以只覆盖部分字段,导致不一致的状态! + var bad_buf: BufferA = .{ .len = 10 }; // ptr 是 null,但 len 是 10 + // #endregion faulty_default_problem +}; + +const FaultyDefaultSolution = struct { + // #region faulty_default_solution + /// `ptr` 指向 `[len]u32` + pub const BufferB = extern struct { + ptr: ?[*]u32, + len: usize, + + // 通过声明提供预定义的有效状态 + pub const empty: BufferB = .{ .ptr = null, .len = 0 }; + }; + + // 安全地创建空 buffer + var empty_buf: BufferB = .empty; + + // 如果要手动指定值,必须同时指定所有字段 + // var custom_buf: BufferB = .{ .ptr = some_ptr, .len = 10 }; + // #endregion faulty_default_solution +}; + +const StdLibArrayList = struct { + // #region stdlib_arraylist + const Container = struct { + // 使用 .empty 而不是 .{} + list: std.ArrayListUnmanaged(i32) = .empty, + }; + + test "ArrayListUnmanaged with decl literal" { + var c: Container = .{}; + defer c.list.deinit(std.testing.allocator); + + try c.list.append(std.testing.allocator, 1); + try c.list.append(std.testing.allocator, 2); + + try std.testing.expectEqual(2, c.list.items.len); + } + // #endregion stdlib_arraylist +}; + +const StdLibDebugAllocator = struct { + // #region stdlib_debug_allocator + pub fn main() !void { + // DebugAllocator 在 0.16 中提供了 .init 声明 + var gpa: std.heap.DebugAllocator(.{}) = .init; + defer _ = gpa.deinit(); + + const allocator = gpa.allocator(); + const ptr = try allocator.alloc(u8, 100); + defer allocator.free(ptr); + + std.debug.print("allocated {} bytes\n", .{ptr.len}); + } + + test "debug allocator with decl literal" { + var gpa: std.heap.DebugAllocator(.{}) = .init; + defer _ = gpa.deinit(); + + const allocator = gpa.allocator(); + const ptr = try allocator.alloc(u8, 100); + defer allocator.free(ptr); + + try std.testing.expectEqual(100, ptr.len); + } + // #endregion stdlib_debug_allocator +}; + +const NamingConflict = struct { + // #region naming_conflict + // 错误:字段和声明同名(此代码无法编译) + // const Bad = struct { + // Value: u32, // 字段 + // const Value = 100; // 声明 - 编译错误! + // }; + + // 正确:遵循命名约定 + const Good = struct { + value: u32, // 字段使用 snake_case + const Value = 100; // 声明使用 PascalCase + }; + // #endregion naming_conflict +}; + +test "basic inference" { + const Point = BasicInference.Point; + const pt: Point = .{ .x = 10, .y = 20 }; + try std.testing.expectEqual(10, pt.x); + try std.testing.expectEqual(20, pt.y); +} + +test "decl literal basic" { + const S = DeclLiteralBasic.S; + const val: S = .default; + try std.testing.expectEqual(123, val.x); +} + +test "decl literal field default" { + const Application = DeclLiteralFieldDefault.Application; + const app1: Application = .{}; + try std.testing.expectEqual(1, app1.settings.x); +} + +test "decl literal function" { + const Point = DeclLiteralFunction.Point; + const p1: Point = .init(5); + try std.testing.expectEqual(5, p1.x); +} + +test "decl literal error union" { + const Buffer = DeclLiteralErrorUnion.Buffer; + var buf: Buffer = try .initCapacity(std.testing.allocator, 10); + defer buf.data.deinit(std.testing.allocator); + buf.data.appendAssumeCapacity(42); + try std.testing.expectEqual(42, buf.data.items[0]); +} + +test "stdlib arraylist" { + const Container = StdLibArrayList.Container; + var c: Container = .{}; + defer c.list.deinit(std.testing.allocator); + try c.list.append(std.testing.allocator, 1); + try std.testing.expectEqual(1, c.list.items.len); +} + +test "stdlib debug allocator" { + var gpa: std.heap.DebugAllocator(.{}) = .init; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + const ptr = try allocator.alloc(u8, 100); + defer allocator.free(ptr); + try std.testing.expectEqual(100, ptr.len); +} diff --git a/course/code/16/struct.zig b/course/code/16/struct.zig index 48ce2a8b..33dca038 100644 --- a/course/code/16/struct.zig +++ b/course/code/16/struct.zig @@ -140,7 +140,7 @@ const SelfReference3 = struct { // #region more_self_reference3 const std = @import("std"); - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa = std.heap.DebugAllocator(.{}){}; // #region deault_self_reference3 const User = struct { diff --git a/course/engineering/build-system.md b/course/engineering/build-system.md index f308297b..bdaff7de 100644 --- a/course/engineering/build-system.md +++ b/course/engineering/build-system.md @@ -154,7 +154,7 @@ Project-Specific Options: 通常,二进制可执行程序的构建结果会输出在 `zig-out/bin` 下,而链接库的构建结果会输出在 `zig-out/lib` 下。 -如果要连接到系统的库,则使用 `exe.linkSystemLibrary`,Zig 内部借助 pkg-config 实现该功能。示例: +如果要连接到系统的库,在 Zig 0.16 的模块化构建 API 中通常使用 `exe.root_module.linkSystemLibrary`,Zig 内部借助 pkg-config 实现该功能。类似地,链接其他库或添加 C/C++ 源文件时,也通常是操作 `root_module`。示例: <<<@/code/release/build_system/system_lib/build.zig @@ -266,9 +266,9 @@ zig 的构建系统还允许我们执行一些额外的命令,例如根据 jso ::: info 🅿️ 提示 -关于头文件的引入,可以使用 `addIncludePath` +关于头文件的引入,可以使用 `root_module.addIncludePath` -针对多个 C 源代码文件,zig 提供了函数 `addCSourceFiles` 用于便捷地添加多个源文件。 +针对多个 C 源代码文件,zig 提供了 `root_module.addCSourceFiles` 用于便捷地添加多个源文件。 ::: diff --git a/course/engineering/package_management.md b/course/engineering/package_management.md index 61033983..2206feb1 100644 --- a/course/engineering/package_management.md +++ b/course/engineering/package_management.md @@ -21,12 +21,9 @@ zig 当前并没有一个中心化存储库,包可以来自任何来源,无 - `name`:当前你所开发的包的名字 - `version`:包的版本,使用 [Semantic Version](https://semver.org/)。 - `fingerprint`: 该值为校验和,它与包的名字有关,使用 `zig build` 时会告诉你应该填什么。 -- `dependencies`:依赖项,内部是一连串的匿名结构体,字段 - `dep_name` 是依赖包的名字, - `url` 是源代码地址, - `hash` 是对应的 hash(源文件内容的 hash), - `path`是不使用源码包而是本地目录时目录的路径。 - 当使用目录方法导入包时就不能使用`url`和`hash`,反之同理。 +- `dependencies`:依赖项,内部是一个匿名结构体;每个字段名就是依赖包名,字段值则是该依赖的配置。 + 常见配置项包括 `url`、`hash` 和 `path`。 + 当使用源码包时填写 `url` 和 `hash`;当使用本地目录时填写 `path`,两种方式不能混用。 - `paths`:显式声明包含的源文件,包含所有则指定为空。 ::: info 🅿️ 提示 diff --git a/course/hello-world.md b/course/hello-world.md index f2152503..e999dd3e 100644 --- a/course/hello-world.md +++ b/course/hello-world.md @@ -42,7 +42,7 @@ Zig 默认可导入三个核心模块: - **参数**:第二个参数是一个元组,你可以将其理解为一个匿名结构体。 ::: warning ⚠️ 注意 -`std.debug.print` 主要用于调试,不推荐在生产环境中使用。因为它会将信息打印到 `stderr`,且在某些构建模式下可能会被编译器优化掉。 +`std.debug.print` 主要用于调试,不推荐在生产环境中作为正式输出接口使用。因为它会将信息打印到 `stderr`,更适合调试信息而不是程序的正常输出。 这只是一个入门示例,接下来我们将探讨更“正确”的打印方式。 ::: @@ -52,7 +52,7 @@ Zig 默认可导入三个核心模块: “打印 Hello, World”看似简单,但在 Zig 中,它能引导我们思考一些底层设计。 -Zig 本身没有内置的 `@print()` 函数,输出功能通常由标准库的 `log` 和 `io` 包提供。`std.debug.print` 是一个特例,主要用于调试。 +Zig 本身没有内置的 `@print()` 函数,输出功能通常由标准库的 `log` 和 `Io` 包提供。`std.debug.print` 是一个特例,主要用于调试。 让我们看一个更规范的例子(**但请注意,此代码同样不建议直接用于生产环境**): @@ -65,9 +65,9 @@ Zig 本身没有内置的 `@print()` 函数,输出功能通常由标准库的 这段代码分别向 `stdout` 和 `stderr` 输出了信息。 - `stdout` (标准输出):用于输出程序的正常信息。写入 `stdout` 的操作可能会失败。 -- `stderr` (标准错误):用于输出错误信息。我们通常假定写入 `stderr` 的操作不会失败(由操作系统保证)。 +- `stderr` (标准错误):用于输出错误信息。写入 `stderr` 同样可能失败,因此示例里也使用了 `try` 来处理错误。 -我们通过 `std.io` 模块获取了标准输出和标准错误的 `writer`,它们提供了 `print` 方法,可以将格式化的字符串写入对应的 I/O 流。 +我们通过 `std.Io.File.stdout()` 和 `std.Io.File.stderr()` 获取标准输出和标准错误,并基于传入的 `io` 实例创建 `writer`。示例中的 `io` 一般来自入口函数 `main(init: std.process.Init)` 中的 `init.io`。 ### 考虑性能:使用缓冲区 @@ -77,17 +77,17 @@ Zig 本身没有内置的 `@print()` 函数,输出功能通常由标准库的 <<<@/code/release/hello_world.zig#three -通过 `std.io.bufferedWriter`,我们为 `stdout` 和 `stderr` 的 `writer` 添加了缓冲功能,从而提高了性能。 +这里直接为 `stdout` 和 `stderr` 各自提供了一块固定缓冲区,`writer` 会先把内容写入缓冲区,再在 `flush` 时真正输出,从而减少系统调用。 ## 更进一步:线程安全 以上代码在单线程环境下工作良好,但在多线程环境中,多个线程同时调用 `print` 可能会导致输出内容交错混乱。 -为了保证线程安全,我们需要为 `writer` 添加锁。 +为了保证线程安全,通常需要在共享 `writer` 的外层自行做同步。 -你可以使用 `std.Thread.Mutex` 来实现一个线程安全的 `writer`。 +在 Zig 0.16 中,常见做法是使用 `std.Io.Mutex`(需要 `Io` 实例)或基于 `std.atomic.Mutex` 的轻量自旋锁,根据具体场景选择。 -我们鼓励你阅读[标准库源码](https://ziglang.org/documentation/master/std/#std.Thread.Mutex)来深入了解其工作原理。 +我们鼓励你阅读[标准库源码](https://ziglang.org/documentation/master/std/#std.Io.Mutex)来深入了解其工作原理。 ## 了解更多 From bb81ed6ed2e7abc83db640ca70ec7bbca8d76f88 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 21 Jun 2026 20:58:46 +0800 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=E5=AF=B9=E9=BD=90=20Zig=200.16=20?= =?UTF-8?q?=E6=95=99=E7=A8=8B=E5=B9=B6=E8=A1=A5=E5=85=A8=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为降低读者在 0.16 迁移期的理解成本,统一更新进阶、基础、 工程与环境章节中的过时表述,明确新旧写法边界。 - 新增并重组 `course/code/release` 下的大量示例与工程模板, 让文档引用的代码可直接复现,减少“文档可读但示例不可跑”的落差。 - 调整 `build/0.16.zig` 的构建逻辑,为 C 互操作示例注入 `addTranslateC` 模块 --- build/0.16.zig | 155 +++--- course/advanced/assembly.md | 4 +- course/advanced/atomic.md | 8 +- course/advanced/interact-with-c.md | 63 ++- course/advanced/memory_manage.md | 10 +- course/advanced/reflection.md | 24 +- course/advanced/type_cast.md | 2 +- course/advanced/undefined_behavior.md | 4 +- course/basic/advanced_type/enum.md | 5 +- course/basic/advanced_type/opaque.md | 2 +- course/basic/advanced_type/string.md | 2 +- course/basic/advanced_type/struct.md | 2 +- course/basic/advanced_type/vector.md | 2 +- course/basic/basic_type/char-and-boolean.md | 2 +- course/basic/basic_type/function.md | 6 +- course/basic/union.md | 4 +- course/code/16/interact_with_c.zig | 13 +- course/code/16/struct.zig | 2 +- course/code/16/switch.zig | 2 +- course/code/release | 1 - course/code/release/array.zig | 136 +++++ course/code/release/assembly.zig | 69 +++ course/code/release/atomic.zig | 50 ++ course/code/release/build_system/README.md | 1 + .../code/release/build_system/basic/build.zig | 17 + .../release/build_system/basic/build.zig.zon | 73 +++ .../release/build_system/basic/src/main.zig | 25 + course/code/release/build_system/build.zig | 88 ++++ .../code/release/build_system/cli/build.zig | 21 + .../release/build_system/cli/build.zig.zon | 73 +++ .../release/build_system/cli/src/main.zig | 25 + .../code/release/build_system/docs/build.zig | 32 ++ .../release/build_system/docs/build.zig.zon | 73 +++ .../release/build_system/docs/src/root.zig | 10 + .../release/build_system/embedfile/build.zig | 44 ++ .../build_system/embedfile/build.zig.zon | 73 +++ .../build_system/embedfile/src/hello.txt | 1 + .../build_system/embedfile/src/main.zig | 7 + .../build_system/externalfile/build.zig | 63 +++ .../build_system/externalfile/build.zig.zon | 73 +++ .../build_system/externalfile/src/main.zig | 6 + .../code/release/build_system/lib/build.zig | 31 ++ .../release/build_system/lib/build.zig.zon | 73 +++ .../release/build_system/lib/src/main.zig | 18 + .../release/build_system/lib/src/root.zig | 10 + course/code/release/build_system/main.zig | 5 + .../release/build_system/options/build.zig | 35 ++ .../build_system/options/build.zig.zon | 73 +++ .../release/build_system/options/src/main.zig | 7 + .../code/release/build_system/step/build.zig | 44 ++ .../release/build_system/step/build.zig.zon | 73 +++ .../release/build_system/step/src/main.zig | 18 + .../release/build_system/system_lib/build.zig | 32 ++ .../build_system/system_lib/build.zig.zon | 73 +++ .../build_system/system_lib/src/main.zig | 5 + .../code/release/build_system/test/build.zig | 46 ++ .../release/build_system/test/build.zig.zon | 73 +++ .../release/build_system/test/src/main.zig | 25 + .../release/build_system/test/src/root.zig | 10 + .../release/build_system/tinytetris/build.zig | 59 +++ .../build_system/tinytetris/build.zig.zon | 73 +++ .../build_system/tinytetris/src/main.cc | 161 ++++++ course/code/release/char-and-boolean.zig | 61 +++ course/code/release/comptime.zig | 170 ++++++ course/code/release/decision.zig | 164 ++++++ course/code/release/defer.zig | 19 + course/code/release/define_variable.zig | 211 ++++++++ course/code/release/echo_tcp_server.zig | 80 +++ course/code/release/enum.zig | 161 ++++++ course/code/release/error_handle.zig | 263 ++++++++++ course/code/release/function.zig | 80 +++ course/code/release/hello_world.zig | 60 +++ .../release/import_dependency_build/build.zig | 56 ++ .../import_dependency_build/pkg1/build.zig | 5 + .../pkg1/build.zig.zon | 15 + .../import_dependency_build/pkg2/build.zig | 9 + .../pkg2/build.zig.zon | 10 + course/code/release/import_vcpkg/build.zig | 30 ++ .../code/release/import_vcpkg/build.zig.zon | 73 +++ course/code/release/import_vcpkg/src/main.zig | 25 + course/code/release/interact_with_c.zig | 47 ++ course/code/release/loop.zig | 309 +++++++++++ course/code/release/memory_manager.zig | 234 +++++++++ course/code/release/number.zig | 58 +++ course/code/release/opaque.zig | 11 + course/code/release/optional_type.zig | 59 +++ .../package_management_exporter/Readme.md | 1 + .../package_management_exporter/build.zig | 14 + .../package_management_exporter/build.zig.zon | 15 + .../package_management_exporter/src/root.zig | 3 + .../package_management_importer/Readme.md | 1 + .../package_management_importer/build.zig | 32 ++ .../package_management_importer/build.zig.zon | 27 + .../package_management_importer/src/main.zig | 22 + course/code/release/pointer.zig | 266 ++++++++++ course/code/release/reflection.zig | 270 ++++++++++ course/code/release/result-location.zig | 390 ++++++++++++++ course/code/release/slice.zig | 69 +++ course/code/release/string.zig | 80 +++ course/code/release/struct.zig | 489 ++++++++++++++++++ course/code/release/switch.zig | 283 ++++++++++ course/code/release/type-cast.zig | 276 ++++++++++ course/code/release/union.zig | 136 +++++ course/code/release/unit_test.zig | 82 +++ course/code/release/unreachable.zig | 9 + course/code/release/vector.zig | 134 +++++ course/engineering/build-system.md | 18 +- course/engineering/package_management.md | 14 +- course/environment/editor.md | 8 +- course/environment/zig-command.md | 2 +- course/examples/echo_tcp_server.md | 44 +- course/update/0.16.0-description.md | 6 +- course/update/upgrade-0.16.0.md | 397 ++++++++++---- 113 files changed, 7211 insertions(+), 304 deletions(-) delete mode 120000 course/code/release create mode 100644 course/code/release/array.zig create mode 100644 course/code/release/assembly.zig create mode 100644 course/code/release/atomic.zig create mode 100644 course/code/release/build_system/README.md create mode 100644 course/code/release/build_system/basic/build.zig create mode 100644 course/code/release/build_system/basic/build.zig.zon create mode 100644 course/code/release/build_system/basic/src/main.zig create mode 100644 course/code/release/build_system/build.zig create mode 100644 course/code/release/build_system/cli/build.zig create mode 100644 course/code/release/build_system/cli/build.zig.zon create mode 100644 course/code/release/build_system/cli/src/main.zig create mode 100644 course/code/release/build_system/docs/build.zig create mode 100644 course/code/release/build_system/docs/build.zig.zon create mode 100644 course/code/release/build_system/docs/src/root.zig create mode 100644 course/code/release/build_system/embedfile/build.zig create mode 100644 course/code/release/build_system/embedfile/build.zig.zon create mode 100644 course/code/release/build_system/embedfile/src/hello.txt create mode 100644 course/code/release/build_system/embedfile/src/main.zig create mode 100644 course/code/release/build_system/externalfile/build.zig create mode 100644 course/code/release/build_system/externalfile/build.zig.zon create mode 100644 course/code/release/build_system/externalfile/src/main.zig create mode 100644 course/code/release/build_system/lib/build.zig create mode 100644 course/code/release/build_system/lib/build.zig.zon create mode 100644 course/code/release/build_system/lib/src/main.zig create mode 100644 course/code/release/build_system/lib/src/root.zig create mode 100644 course/code/release/build_system/main.zig create mode 100644 course/code/release/build_system/options/build.zig create mode 100644 course/code/release/build_system/options/build.zig.zon create mode 100644 course/code/release/build_system/options/src/main.zig create mode 100644 course/code/release/build_system/step/build.zig create mode 100644 course/code/release/build_system/step/build.zig.zon create mode 100644 course/code/release/build_system/step/src/main.zig create mode 100644 course/code/release/build_system/system_lib/build.zig create mode 100644 course/code/release/build_system/system_lib/build.zig.zon create mode 100644 course/code/release/build_system/system_lib/src/main.zig create mode 100644 course/code/release/build_system/test/build.zig create mode 100644 course/code/release/build_system/test/build.zig.zon create mode 100644 course/code/release/build_system/test/src/main.zig create mode 100644 course/code/release/build_system/test/src/root.zig create mode 100644 course/code/release/build_system/tinytetris/build.zig create mode 100644 course/code/release/build_system/tinytetris/build.zig.zon create mode 100644 course/code/release/build_system/tinytetris/src/main.cc create mode 100644 course/code/release/char-and-boolean.zig create mode 100644 course/code/release/comptime.zig create mode 100644 course/code/release/decision.zig create mode 100644 course/code/release/defer.zig create mode 100644 course/code/release/define_variable.zig create mode 100644 course/code/release/echo_tcp_server.zig create mode 100644 course/code/release/enum.zig create mode 100644 course/code/release/error_handle.zig create mode 100644 course/code/release/function.zig create mode 100644 course/code/release/hello_world.zig create mode 100644 course/code/release/import_dependency_build/build.zig create mode 100644 course/code/release/import_dependency_build/pkg1/build.zig create mode 100644 course/code/release/import_dependency_build/pkg1/build.zig.zon create mode 100644 course/code/release/import_dependency_build/pkg2/build.zig create mode 100644 course/code/release/import_dependency_build/pkg2/build.zig.zon create mode 100644 course/code/release/import_vcpkg/build.zig create mode 100644 course/code/release/import_vcpkg/build.zig.zon create mode 100644 course/code/release/import_vcpkg/src/main.zig create mode 100644 course/code/release/interact_with_c.zig create mode 100644 course/code/release/loop.zig create mode 100644 course/code/release/memory_manager.zig create mode 100644 course/code/release/number.zig create mode 100644 course/code/release/opaque.zig create mode 100644 course/code/release/optional_type.zig create mode 100644 course/code/release/package_management_exporter/Readme.md create mode 100644 course/code/release/package_management_exporter/build.zig create mode 100644 course/code/release/package_management_exporter/build.zig.zon create mode 100644 course/code/release/package_management_exporter/src/root.zig create mode 100644 course/code/release/package_management_importer/Readme.md create mode 100644 course/code/release/package_management_importer/build.zig create mode 100644 course/code/release/package_management_importer/build.zig.zon create mode 100644 course/code/release/package_management_importer/src/main.zig create mode 100644 course/code/release/pointer.zig create mode 100644 course/code/release/reflection.zig create mode 100644 course/code/release/result-location.zig create mode 100644 course/code/release/slice.zig create mode 100644 course/code/release/string.zig create mode 100644 course/code/release/struct.zig create mode 100644 course/code/release/switch.zig create mode 100644 course/code/release/type-cast.zig create mode 100644 course/code/release/union.zig create mode 100644 course/code/release/unit_test.zig create mode 100644 course/code/release/unreachable.zig create mode 100644 course/code/release/vector.zig diff --git a/build/0.16.zig b/build/0.16.zig index f672acc7..7674b0a5 100644 --- a/build/0.16.zig +++ b/build/0.16.zig @@ -31,77 +31,94 @@ pub fn build(b: *Build) void { log.err("iterate examples_path failed, err is {}", .{err}); std.process.exit(1); }) |entry| { - // get the entry name, entry can be file or directory - const output_name = if (std.mem.endsWith(u8, entry.name, ".zig")) - entry.name[0 .. entry.name.len - ".zig".len] - else - entry.name; - if (entry.kind == .file) { - // connect path - const path = std.fs.path.join(b.allocator, &[_][]const u8{ relative_path, entry.name }) catch |err| { - log.err("fmt path for examples failed, err is {}", .{err}); - std.process.exit(1); - }; - - // build exe - const exe = b.addExecutable(.{ - .name = output_name, - .root_module = b.addModule(output_name, .{ - .root_source_file = b.path(path), - .target = target, - .optimize = optimize, - }), + // get the entry name, entry can be file or directory + const output_name = if (std.mem.endsWith(u8, entry.name, ".zig")) + entry.name[0 .. entry.name.len - ".zig".len] + else + entry.name; + if (entry.kind == .file) { + // connect path + const path = std.fs.path.join(b.allocator, &[_][]const u8{ relative_path, entry.name }) catch |err| { + log.err("fmt path for examples failed, err is {}", .{err}); + std.process.exit(1); + }; + + const imports: []const std.Build.Module.Import = if (std.mem.eql(u8, entry.name, "interact_with_c.zig")) imports: { + const c_header = b.addWriteFiles().add("interact_with_c.h", + \\#define _NO_CRT_STDIO_INLINE 1 + \\#include + ); + const translate_c = b.addTranslateC(.{ + .root_source_file = c_header, + .target = target, + .optimize = optimize, }); - exe.root_module.linkSystemLibrary("c", .{}); - - if (exe.root_module.resolved_target.?.result.os.tag == .windows and std.mem.eql(u8, "echo_tcp_server.zig", entry.name)) { - std.log.info("link ws2_32 for {s}", .{entry.name}); - exe.root_module.linkSystemLibrary("ws2_32", .{}); - } - // add to default install - b.installArtifact(exe); - - // build test - const test_name = std.fmt.allocPrint(b.allocator, "{s}_test", .{output_name}) catch |err| { - log.err("fmt test name failed, err is {}", .{err}); - std.process.exit(1); - }; - const unit_tests = b.addTest(.{ - .root_module = b.addModule(test_name, .{ - .root_source_file = b.path(path), - .target = target, - .optimize = optimize, - }), - }); - - // add to default install - b.getInstallStep().dependOn(&b.addRunArtifact(unit_tests).step); - } else if (entry.kind == .directory) { - - // build child process - // build cwd - const cwd = std.fs.path.join(b.allocator, &[_][]const u8{ - full_path, - entry.name, - }) catch |err| { - log.err("fmt path for examples failed, err is {}", .{err}); - std.process.exit(1); + break :imports &[_]std.Build.Module.Import{ + .{ .name = "c", .module = translate_c.createModule() }, }; - - // open entry dir - const entry_dir = std.Io.Dir.openDirAbsolute(io, cwd, .{}) catch unreachable; - defer entry_dir.close(io); - - entry_dir.access(io, "build.zig", .{}) catch { - log.err("not found build.zig in path {s}", .{cwd}); - std.process.exit(1); - }; - - var child = std.process.spawn(io, .{ - .argv = &args, - .cwd = .{ .path = cwd }, - }) catch unreachable; - _ = child.wait(io) catch unreachable; + } else &.{}; + + // build exe + const exe = b.addExecutable(.{ + .name = output_name, + .root_module = b.addModule(output_name, .{ + .root_source_file = b.path(path), + .target = target, + .optimize = optimize, + .imports = imports, + }), + }); + exe.root_module.linkSystemLibrary("c", .{}); + + if (exe.root_module.resolved_target.?.result.os.tag == .windows and std.mem.eql(u8, "echo_tcp_server.zig", entry.name)) { + std.log.info("link ws2_32 for {s}", .{entry.name}); + exe.root_module.linkSystemLibrary("ws2_32", .{}); } + // add to default install + b.installArtifact(exe); + + // build test + const test_name = std.fmt.allocPrint(b.allocator, "{s}_test", .{output_name}) catch |err| { + log.err("fmt test name failed, err is {}", .{err}); + std.process.exit(1); + }; + const unit_tests = b.addTest(.{ + .root_module = b.addModule(test_name, .{ + .root_source_file = b.path(path), + .target = target, + .optimize = optimize, + .imports = imports, + }), + }); + + // add to default install + b.getInstallStep().dependOn(&b.addRunArtifact(unit_tests).step); + } else if (entry.kind == .directory) { + + // build child process + // build cwd + const cwd = std.fs.path.join(b.allocator, &[_][]const u8{ + full_path, + entry.name, + }) catch |err| { + log.err("fmt path for examples failed, err is {}", .{err}); + std.process.exit(1); + }; + + // open entry dir + const entry_dir = std.Io.Dir.openDirAbsolute(io, cwd, .{}) catch unreachable; + defer entry_dir.close(io); + + entry_dir.access(io, "build.zig", .{}) catch { + log.err("not found build.zig in path {s}", .{cwd}); + std.process.exit(1); + }; + + var child = std.process.spawn(io, .{ + .argv = &args, + .cwd = .{ .path = cwd }, + }) catch unreachable; + _ = child.wait(io) catch unreachable; + } } } diff --git a/course/advanced/assembly.md b/course/advanced/assembly.md index 2f1df5f4..72aea342 100644 --- a/course/advanced/assembly.md +++ b/course/advanced/assembly.md @@ -51,11 +51,11 @@ outline: deep <<<@/code/release/assembly.zig#inline_assembly -上面这段代码是通过内联汇编实现在 x86-64 linux 下输出 `hello world`,接下来讲解一下它们的组成和使用。 +上面这段示例当前没有直接执行内联汇编:旧的 `syscall` 写法保留在注释中,实际路径只调用 `std.process.exit(0)`,避免把未迁移的内联汇编语法误当作 Zig 0.16 可运行示例。 内联汇编是以 `asm` 关键字开头的一个表达式,这说明它可以返回值(也可以不返回值),`volatile` 关键字会通知编译器,内联汇编的表达式会被某些编译器未知的因素更改(例如操作系统,硬件 MMIO 或者其他线程等等),这样编译器就不会额外优化这段内联汇编。 -上面就是基本的内联汇编的一个外部结构说明,接下来我们介绍具体的内部结构: +下面是内联汇编表达式的一般结构: ```zig asm volatile ("assembly code" diff --git a/course/advanced/atomic.md b/course/advanced/atomic.md index 64ebb469..bf173044 100644 --- a/course/advanced/atomic.md +++ b/course/advanced/atomic.md @@ -9,7 +9,7 @@ outline: deep 目前 Zig 提供了一些内建函数来进行原子操作,并且提供了 `std.atomic` 命名空间来实现内存排序、原子数据结构。 > [!TIP] -> 该部分内容更适合在单片机或者某些系统级组件开发上使用,常规使用可以使用 `std.Thread` 命名空间下的类型,包含常规的 `Mutex`,`Condition`,`ResetEvent`,`WaitGroup`等等。 +> 该部分内容更适合在单片机或者某些系统级组件开发上使用,常规使用可以使用 `std.Io` 命名空间下的同步原语,例如 `Mutex`、`Condition`、`ResetEvent`,以及 `std.Io.Group` / `std.Io.async` 这类任务同步接口。 ## 内建函数 @@ -122,7 +122,7 @@ outline: deep - **`ref()` 方法**:使用 `.monotonic` 顺序递增计数器。由于只是简单地增加计数,不需要与其他内存操作建立同步关系。 - **`unref()` 方法**:使用 `.release` 顺序递减计数器,确保在计数递减之前的所有内存操作对其他线程可见。当计数减到 1 时,使用 `.acquire` 加载来获取之前所有 `unref()` 操作形成的 release 序列,确保可以安全地调用清理函数。 -这种模式常用于智能指针、共享资源管理等场景,是替代 `Mutex` 的轻量级线程安全方案 +这种模式常用于智能指针、共享资源管理等场景,是替代 `std.Io.Mutex` 的轻量级线程安全方案 ### `spinLoopHint` 自旋锁 @@ -142,10 +142,10 @@ outline: deep - 当等待某个原子变量的值发生变化时 - 当预期等待时间非常短(纳秒到微秒级别)时 -**与 Mutex 的选择**: +**与 `std.Io.Mutex` 的选择**: - **自旋等待**:适合极短时间的等待,避免线程上下文切换的开销 -- **Mutex**:适合等待时间不确定或较长的场景,会让出 CPU 给其他线程 +- **`std.Io.Mutex`**:适合等待时间不确定或较长的场景,会让出 CPU 给其他线程 ::: warning ⚠️ 警告 不当使用自旋等待会导致 CPU 资源浪费。如果等待时间较长或不确定,应使用 `std.Io.Mutex` 这类会让出 CPU 的同步原语。 diff --git a/course/advanced/interact-with-c.md b/course/advanced/interact-with-c.md index b14fafb9..694fc536 100644 --- a/course/advanced/interact-with-c.md +++ b/course/advanced/interact-with-c.md @@ -36,30 +36,52 @@ Zig 定义了几个对应 C ABI 的基本类型: ## C Header 导入 -C 语言共享类型通常是通过引入头文件实现,这点在 zig 中可以无缝做到,得益于 zig 的 **translate-c** 功能。 +C 语言共享类型通常通过引入头文件实现。Zig 0.16 起推荐把头文件翻译放到 `build.zig` 中:先用 `addTranslateC` 生成模块,再在 Zig 代码里像普通模块一样 `@import("c")`。 -接下来展示一个例子,简单地引入 c 标准库的 `printf` 函数: +`build.zig` 中的核心写法如下: -<<<@/code/release/interact_with_c.zig#cHeaderImport +```zig +const translate_c = b.addTranslateC(.{ + .root_source_file = b.path("src/c.h"), + .target = target, + .optimize = optimize, +}); + +const exe = b.addExecutable(.{ + .name = "example", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "c", .module = translate_c.createModule() }, + }, + }), +}); +exe.root_module.linkSystemLibrary("c", .{}); +``` -::: info 🅿️ 提示 +`src/c.h` 中放需要翻译的 C 头文件: -注意:为了构建这个,我们需要引入 `libc`。在 Zig 0.16 的构建脚本中,可以让对应模块链接 C 标准库,例如 `exe.root_module.linkSystemLibrary("c", .{})`。 +```c +#define _NO_CRT_STDIO_INLINE 1 +#include +``` -或者我们可以手动执行构建:`zig build-exe source.zig -lc` +接下来展示 Zig 调用侧的例子,简单地引入 C 标准库的 `printf` 函数: -::: - -[`@cImport`](https://ziglang.org/documentation/master/#cImport) 函数接受一个表达式作为参数,该表达式会在编译期执行,用于控制预处理器指令并引入头文件。 +<<<@/code/release/interact_with_c.zig#cHeaderImport ::: info 🅿️ 提示 -表达式内应仅包含 [`@cInclude`](https://ziglang.org/documentation/master/#cInclude)、[`@cDefine`](https://ziglang.org/documentation/master/#cDefine)、[`@cUndef`](https://ziglang.org/documentation/master/#cUndef),它们会在编译时进行解析并转换为 C 代码。 +注意:为了构建这个,我们需要引入 `libc`。在 Zig 0.16 的构建脚本中,可以让对应模块链接 C 标准库,例如 `exe.root_module.linkSystemLibrary("c", .{})`。 -通常情况下,应当只存在一个 `@cImport`,这是防止编译器重复调用 clang,并且避免内联函数被重复,只有为了避免符号冲突(两个文件均定义了相同的标识符)和分析具有不同预处理定义的代码时才出现多个 `@cImport`。 +因此通常通过 `zig build` 驱动这个例子;旧的 `@cImport` 单文件代码才适合手动 `zig build-exe source.zig -lc`。 ::: +`@cImport` 仍然保留,但在 Zig 0.16 已进入 deprecated 迁移期;它只适合维护旧的单文件示例或历史代码。新代码不要再把它作为 C 头文件入口,应优先使用上面的 `build.zig` + `@import("c")` 路径。 + ## vcpkg C Lib 导入 > [!IMPORTANT] @@ -75,7 +97,7 @@ C 语言共享类型通常是通过引入头文件实现,这点在 zig 中可 <<<@/code/release/import_vcpkg/build.zig#c_import -假设你想要借用 `gsl` 库来对数值进行傅里叶变换,那么可以先导入 +假设你想要借用 `gsl` 库来对数值进行傅里叶变换,Zig 0.16 新代码应沿用上面的 `addTranslateC` + `@import("c")` 路径。下面这个历史片段仍使用旧的 `@cImport`,仅用于说明要导入的 GSL 头文件,不作为新项目推荐写法: <<<@/code/release/import_vcpkg/src/main.zig#import_gsl @@ -108,26 +130,19 @@ Zig 提供了一个命令行工具 `zig translate-c` 供我们使用,它可以 ::: -### `@cImport` vs `translate-c` +### 构建系统 `translate-c` 与命令行 `translate-c` -事实上,这两者底层实现是一样的,`@cImport` 一般用于使用 C 库时引入头文件,而 `translate-c` 通常是为了修改翻译后的代码,例如:将 `anytype` 修改为更加精确的类型、将 `[*c]T` 指针修改为 `[*]T` 或者 `*T` 来提高类型安全性、启动或者禁用某些运行时的安全性功能。 +构建系统里的 `addTranslateC` 是 Zig 0.16 推荐的头文件导入路径;命令行 `zig translate-c` 更适合一次性查看或手动修改翻译后的代码,例如:将 `anytype` 修改为更加精确的类型、将 `[*c]T` 指针修改为 `[*]T` 或者 `*T` 来提高类型安全性、启动或者禁用某些运行时的安全性功能。 ## C 翻译缓存 -C 翻译功能(无论是通过 `zig translate-c` 还是 `@cImport` 使用)与 Zig 缓存系统集成。使用相同源文件、目标和 `cflags` 的后续构建将使用缓存,而不是重复翻译相同的代码。 +C 翻译功能(通过 `build.zig` 的 `addTranslateC` 或 `zig translate-c` 使用)与 Zig 缓存系统集成。使用相同源文件、目标和 `cflags` 的后续构建将使用缓存,而不是重复翻译相同的代码;构建缓存目录是项目下的 `.zig-cache`。 -要在编译使用 `@cImport` 引入的代码时打印缓存文件的存储位置,请使用 `--verbose-cimport` 参数: +下面这个 Zig 文件片段是 `addTranslateC` 生成模块后的调用侧: <<<@/code/release/interact_with_c.zig#cTranslate -```sh -$ zig build-exe test.zig -lc --verbose-cimport -info(compilation): C import source: /home/username/.cache/zig/o/6f35761b17b87ee4c9f26e643a06e289/cimport.h -info(compilation): C import .d file: /home/username/.cache/zig/o/6f35761b17b87ee4c9f26e643a06e289/cimport.h.d -info(compilation): C import output: /home/username/.cache/zig/o/86899cd499e4c3f94aa141e400ac265f/cimport.zig -``` - -`cimport.h` 包含要翻译的文件(通过调用 `@cInclude`、`@cDefine` 和 `@cUndef` 构建),`cimport.h.d` 是文件依赖项列表,`cimport.zig` 包含翻译后的代码。 +如果你还在维护旧的 `@cImport` 代码,`--verbose-cimport` 可以临时用于查看旧导入缓存位置,便于迁移或排查。 ## C 翻译错误 diff --git a/course/advanced/memory_manage.md b/course/advanced/memory_manage.md index a0fe7e98..f0d18a80 100644 --- a/course/advanced/memory_manage.md +++ b/course/advanced/memory_manage.md @@ -66,15 +66,9 @@ outline: deep ## `FixedBufferAllocator` -这个分配器是固定大小的内存缓冲区,无法扩容,常常在你需要缓冲某些东西时使用。注意默认情况下它不是线程安全的;而在 Zig 0.16 中,`ThreadSafeAllocator` 已被移除。如果你需要跨线程共享同一个 `FixedBufferAllocator`,应当像示例那样在外层自行加锁,或者直接改用更适合并发场景的 `SmpAllocator`。 +这个分配器是固定大小的内存缓冲区,无法扩容,常常在你需要缓冲某些东西时使用。注意默认情况下它不是线程安全的;而在 Zig 0.16 中,旧的线程安全包装分配器已被移除。如果只是需要线程安全分配,优先使用更适合并发场景的 `SmpAllocator`;如果必须跨线程共享同一个 `FixedBufferAllocator`,应在调用方使用与执行模型匹配的锁(例如 `std.Io.Mutex`)保护临界区。 -::: code-group - -<<<@/code/release/memory_manager.zig#FixedBufferAllocator [default] - -<<<@/code/release/memory_manager.zig#ThreadSafeFixedBufferAllocator [thread_safe] - -::: +<<<@/code/release/memory_manager.zig#FixedBufferAllocator ## `ArenaAllocator` diff --git a/course/advanced/reflection.md b/course/advanced/reflection.md index 659f8b66..faec1d91 100644 --- a/course/advanced/reflection.md +++ b/course/advanced/reflection.md @@ -56,17 +56,17 @@ main.T.Y 该函数返回一个 [`std.builtin.Type`](https://ziglang.org/documentation/master/std/#std.builtin.Type),它包含了此类型的所有信息。 -它是一个联合类型,有 `Struct`, `Union`, `Enum`, `ErrorSet` 等变体来储存结构体、联合、枚举、错误集等类型的类型信息。要判断类型的种类,可以使用 `switch` 或直接访问相应变体来断言之。 +它是一个联合类型,使用小写的联合标签来表示具体类型信息;遇到 Zig 关键字时需要使用转义字段名,例如 `@"struct"`、`@"union"`、`@"enum"`,整数类型则是 `.int`。要判断类型的种类,可以使用 `switch` 或直接访问相应标签来断言之。 对结构、联合、枚举和错误集合,它保证信息中字段的顺序与源码中出现的顺序相同。 对结构、联合、枚举和透明类型,它保证信息中声明的顺序与源码中出现的顺序相同。 -如以下示例中,首先使用`@typeInfo` 来获取类型 `T` 的信息,然后将其断言为一个 `Struct` 类型,最后用 `inline for` 输出其字段值。 +如以下示例中,首先使用 `@typeInfo` 来获取类型 `T` 的信息,然后将其断言为一个 `@"struct"` 类型,最后用 `inline for` 输出其字段值。 <<<@/code/release/reflection.zig#typeInfo -需要注意的是,我们必须使用 `inline for` 才能编译通过,这是因为结构体的 **“字段类型”** [`std.builtin.Type.StructField`](https://ziglang.org/documentation/master/std/#std.builtin.Type.StructField)中的一个字段是 `comptime_int`类型,使得 StructField 没有运行时大小,从而不能在运行时遍历其数组,必须用 `inline for` 在编译期计算。 +需要注意的是,示例必须使用 `inline for` 才能编译通过,这是因为我们读取了每个字段的 `type`。在 Zig 0.16 中,`std.builtin.Type.StructField` 本身可以作为运行时大小的字段信息读取;只有像字段类型这样的 comptime-only 信息,才需要在编译期用 `inline for` 处理。 ::: warning @@ -78,7 +78,7 @@ main.T.Y <<<@/code/release/reflection.zig#TypeInfo2 -在以下示例中,使用 `@typeInfo` 获得一个结构体的信息,并使用 `@Type` 构造一个新的类型。构造的新结构体类型和原结构体的字段名和顺序相同,但结构体的内存布局被改为 extern,且每个字段的对齐被改为 1。 +在以下示例中,使用 `@typeInfo` 获得一个结构体的信息,并使用 `@Struct` 构造一个新的类型。构造的新结构体类型和原结构体的字段名和顺序相同,但结构体的内存布局被改为 extern,且每个字段的对齐被改为 1。 <<<@/code/release/reflection.zig#TypeInfo3 @@ -130,17 +130,11 @@ main.T.Y zig 除了获取类型信息外,还提供了在编译期构建全新类型的能力,允许我们通过非常规的方式来声明一个类型。 -构建新类型的能力主要依赖于 `@Type`。 +Zig 0.16 移除了旧的 `@Type`。现在应根据要构造的类型选择具体的内建函数,例如 `@Struct`、`@Union`、`@Enum`、`@Int`、`@Tuple`、`@Pointer`、`@Fn` 和 `@EnumLiteral`。 -### `@Type` +### 类型构造内建函数 -该函数实际上就是 `@typeInfo` 的反函数,它将类型信息具体化为一个类型。 - -函数的原型为: - -`@Type(comptime info: std.builtin.Type) type` - -参数的具体类型可以参考 [此处](https://ziglang.org/documentation/master/std/#std.builtin.Type)。 +这些函数把对应类型的描述直接具体化为一个类型。参数形式请参考各内建函数的文档。 以下示例为我们构建一个新的结构体: @@ -148,9 +142,7 @@ zig 除了获取类型信息外,还提供了在编译期构建全新类型的 ::: info 🅿️ 提示 -除了常见的类型外,还有以下特殊类型: - -- 关于枚举,还存在一个 `EnumLiteral` 类型,可以称之为枚举字面量,详细说明见 [枚举](../basic/advanced_type/enum.md#enumliteral)。 +除了常见的类型构造函数外,枚举字面量使用 `@EnumLiteral` 构造,详细说明见 [枚举](../basic/advanced_type/enum.md#enumliteral)。 ::: diff --git a/course/advanced/type_cast.md b/course/advanced/type_cast.md index 64c25561..5915fea0 100644 --- a/course/advanced/type_cast.md +++ b/course/advanced/type_cast.md @@ -118,7 +118,7 @@ undefined 是一个神奇的值,它可以赋值给所有类型,代表这个 - [`@intFromBool`](https://ziglang.org/documentation/master/#intFromBool) 将 `true` 转换为 `1`,`false` 转换为 `0` - [`@intFromEnum`](https://ziglang.org/documentation/master/#intFromEnum) 获取枚举值或联合标记对应的整数值 - [`@intFromError`](https://ziglang.org/documentation/master/#intFromError) 获取对应错误的整数值 -- [`@intFromFloat`](https://ziglang.org/documentation/master/#intFromFloat) 获取浮点数的整数部分 +- [`@trunc`](https://ziglang.org/documentation/master/#trunc) 将浮点数向零取整;结果类型由上下文决定,可以直接得到整数类型。需要其他舍入方式时使用 `@floor`、`@ceil` 或 `@round` - [`@intFromPtr`](https://ziglang.org/documentation/master/#intFromPtr) 获取指针指向的地址(整数 `usize`),这在嵌入式开发和内核开发时很常用 - [`@ptrFromInt`](https://ziglang.org/documentation/master/#ptrFromInt) 根据整数 `usize` 来获取对应的指针,这在嵌入式开发和内核开发时很常用 - [`@ptrCast`](https://ziglang.org/documentation/master/#ptrCast) 不同的指针类型之间进行显式强制转换 diff --git a/course/advanced/undefined_behavior.md b/course/advanced/undefined_behavior.md index b5dac658..46772013 100644 --- a/course/advanced/undefined_behavior.md +++ b/course/advanced/undefined_behavior.md @@ -194,11 +194,11 @@ f = Foo{ .float = 12.34 }; ## 浮点转换整数发生越界 -当将浮点数转换为整数时,如果浮点数的值超出了整数类型的范围,就会发生非法越界,例如: +当使用 `@trunc`、`@floor`、`@ceil` 或 `@round` 把浮点数转换为整数时,如果浮点数的值超出了整数类型的范围,就会发生非法越界,例如: ```zig const float: f32 = 4294967296; -const int: i32 = @intFromFloat(float); +const int: i32 = @trunc(float); ``` ## 指针强制转换为 Null diff --git a/course/basic/advanced_type/enum.md b/course/basic/advanced_type/enum.md index af0d5d49..026f9d95 100644 --- a/course/basic/advanced_type/enum.md +++ b/course/basic/advanced_type/enum.md @@ -72,9 +72,8 @@ Zig 允许我们定义非详尽枚举,即在定义时无需列出所有可能 ::: -Zig 还包含一个特殊的类型 `EnumLiteral`,它是 [`std.builtin.Type`](https://ziglang.org/documentation/master/std/#std.builtin.Type) 的一部分。 - -我们可以称之为“枚举字面量”。它是一个与 `enum` 完全不同的类型。可以查看 Zig 类型系统对 `enum` 的[定义](https://ziglang.org/documentation/master/std/#std.builtin.Type.Enum),其中并不包含 `EnumLiteral`。 +Zig 还包含一个特殊的类型 `EnumLiteral`,可以通过内建函数 `@EnumLiteral()` 得到。 +我们可以称之为“枚举字面量”。它是一个与 `enum` 完全不同的类型,通常用于表示 `.foo` 这样的枚举字面量值。 它的具体用法如下: diff --git a/course/basic/advanced_type/opaque.md b/course/basic/advanced_type/opaque.md index 22b19003..bf558351 100644 --- a/course/basic/advanced_type/opaque.md +++ b/course/basic/advanced_type/opaque.md @@ -29,7 +29,7 @@ outline: deep ```zig // 对应 C 中的 typedef struct FILE FILE; const FILE = opaque {}; -const c = @cImport(@cInclude("stdio.h")); +// C 头文件推荐在 build.zig 中用 addTranslateC 翻译后,再在 Zig 代码里 @import("c")。 // 使用不透明指针 fn readFile(file: *FILE) void { diff --git a/course/basic/advanced_type/string.md b/course/basic/advanced_type/string.md index da563268..ce4d4f36 100644 --- a/course/basic/advanced_type/string.md +++ b/course/basic/advanced_type/string.md @@ -103,4 +103,4 @@ var msg = "banana"; 第二种是手动定义一个常量 `u8` 数组,并由编译器推断其长度。 -第三种是手动定义一个常量 `u8` 数组并获取其指针,编译器会将其自动转换为常量 `u8` 切片。 +第三种是手动定义一个常量 `u8` 数组并获取其指针,编译器会将其自动转换为 `[]const u8` 切片。 diff --git a/course/basic/advanced_type/struct.md b/course/basic/advanced_type/struct.md index 46db9515..6c93a4c8 100644 --- a/course/basic/advanced_type/struct.md +++ b/course/basic/advanced_type/struct.md @@ -260,7 +260,7 @@ 在 64 位系统上,Foo 的内存布局是: ```sh -| 4(i32) | 8(pointer) | 4(padding) | +| 4(i32) | 8(usize) | 4(padding) | ``` 额外的讨论信息:[github issue #20265](https://github.com/ziglang/zig/issues/20265) diff --git a/course/basic/advanced_type/vector.md b/course/basic/advanced_type/vector.md index e83a677e..796faa12 100644 --- a/course/basic/advanced_type/vector.md +++ b/course/basic/advanced_type/vector.md @@ -12,7 +12,7 @@ - 位操作符 (`>>`, `<<`, `&`, `|`,`~`, ... ) - 比较运算符 (`<`, `>`, `==`, ...) -禁止混合使用标量(单个数字)和向量进行数学运算。Zig 提供了 [`@splat`](https://ziglang.org/documentation/master/#splat) 内建函数,可以方便地将标量转换为向量。同时,可以使用 [`@reduce`](https://ziglang.org/documentation/master/#reduce) 和数组索引语法将向量转换为标量。向量还支持直接赋值给已知长度的固定长度数组。如果需要重新排列元素,可以使用 [`@shuffle`](https://ziglang.org/documentation/master/#shuffle) 和 [`@select`](https://ziglang.org/documentation/master/#select) 函数。 +禁止混合使用标量(单个数字)和向量进行数学运算。Zig 提供了 [`@splat`](https://ziglang.org/documentation/master/#splat) 内建函数,可以方便地将标量转换为向量。同时,可以使用 [`@reduce`](https://ziglang.org/documentation/master/#reduce) 和编译期已知的下标将向量转换为标量;如果下标只能在运行时确定,需要先把向量转换为数组。向量还支持直接赋值给已知长度的固定长度数组。如果需要重新排列元素,可以使用 [`@shuffle`](https://ziglang.org/documentation/master/#shuffle) 和 [`@select`](https://ziglang.org/documentation/master/#select) 函数。 <<<@/code/release/vector.zig#basic diff --git a/course/basic/basic_type/char-and-boolean.md b/course/basic/basic_type/char-and-boolean.md index 43bf586c..e426adea 100644 --- a/course/basic/basic_type/char-and-boolean.md +++ b/course/basic/basic_type/char-and-boolean.md @@ -30,7 +30,7 @@ Zig 中没有专门的字符类型。与之最接近的是 Unicode 码位字面 ### 字符串字面量 -Zig 中的字符串字面量是一个[单项指针](../advanced_type/pointer#单项指针),它指向将字符串进行 UTF-8 编码后得到的字节数组。这个字节数组与 C 语言中的字符串类似,都是[以 NUL 字符结尾](../advanced_type/array#哨兵数组)的。因此,字符串字面量既可以隐式转换为 `u8` 切片,也可以隐式转换为以 0 结尾的指针。 +Zig 中的字符串字面量是一个[单项指针](../advanced_type/pointer#单项指针),它指向将字符串进行 UTF-8 编码后得到的字节数组。这个字节数组与 C 语言中的字符串类似,都是[以 NUL 字符结尾](../advanced_type/array#哨兵数组)的。因此,字符串字面量既可以隐式转换为 `[]const u8` 切片,也可以隐式转换为以 0 结尾的 `[*:0]const u8` 指针。 <<<@/code/release/char-and-boolean.zig#string-literal diff --git a/course/basic/basic_type/function.md b/course/basic/basic_type/function.md index 182958eb..c66f0db9 100644 --- a/course/basic/basic_type/function.md +++ b/course/basic/basic_type/function.md @@ -153,11 +153,11 @@ closure() # 输出:Hello, World! `extern` 用于引用非 Zig 实现的库,而 `export` 则是 Zig 将函数对外暴露供其他语言使用! ::: -### `@setCold` +### `@branchHint(.cold)` -`@setCold(comptime is_cold: bool) void` +`@branchHint(comptime hint: std.builtin.BranchHint) void` -告诉优化器当前函数很少被调用(或不被调用)。该函数仅在函数作用域内有效。 +使用 `@branchHint(.cold)` 告诉优化器当前分支或函数很少被调用(或不被调用)。 <<<@/code/release/function.zig#abort diff --git a/course/basic/union.md b/course/basic/union.md index db2091ec..3929ea8f 100644 --- a/course/basic/union.md +++ b/course/basic/union.md @@ -20,7 +20,7 @@ outline: deep :::info 🅿️ 提示 -需要注意的是,Zig 不保证普通联合类型在内存中的具体表现形式。如果需要确保其内存布局与 C 兼容,可以使用 `extern union` 或 `packed union`。 +需要注意的是,Zig 不保证普通联合类型在内存中的具体表现形式。如果需要确保其内存布局与目标 C ABI 兼容,应使用 `extern union`;`packed union` 用于位级紧凑布局,不等同于 C ABI 布局。 ::: @@ -92,4 +92,4 @@ Zig 也支持自动推断联合类型: ## `packed union` -`packed union` 保证其内存布局与声明顺序相同,并且尽可能紧凑。具体用法请参见 [`packed struct`](advanced_type/struct.md#packed) 部分。 +`packed union(uN)` 用于位级紧凑布局,需要显式指定整数作为背后类型,并且所有字段都必须刚好占满该位宽,不能留下未使用位;字段中也不能包含指针。具体用法请参见 [`packed struct`](advanced_type/struct.md#packed) 部分。 diff --git a/course/code/16/interact_with_c.zig b/course/code/16/interact_with_c.zig index e45f817e..7ae51e88 100644 --- a/course/code/16/interact_with_c.zig +++ b/course/code/16/interact_with_c.zig @@ -4,10 +4,8 @@ pub fn main() !void { const cHeaderImport = struct { // #region cHeaderImport - const c = @cImport({ - @cDefine("_NO_CRT_STDIO_INLINE", "1"); - @cInclude("stdio.h"); - }); + // 使用 build.zig 的 addTranslateC 生成名为 "c" 的模块后导入 + const c = @import("c"); pub fn main() void { _ = c.printf("hello\n"); } @@ -16,11 +14,8 @@ const cHeaderImport = struct { const cTranslate = struct { // #region cTranslate - // 示例文件 - const c = @cImport({ - @cDefine("_NO_CRT_STDIO_INLINE", "1"); - @cInclude("stdio.h"); - }); + // 使用 build.zig 的 addTranslateC 生成名为 "c" 的模块后导入 + const c = @import("c"); pub fn main() void { _ = c; } diff --git a/course/code/16/struct.zig b/course/code/16/struct.zig index 33dca038..0baf2e22 100644 --- a/course/code/16/struct.zig +++ b/course/code/16/struct.zig @@ -475,7 +475,7 @@ const reorder_struct = struct { const Foo = packed struct { x: i32, - y: [*]i32, // 一个多项指针 + y: usize, // 地址大小的整数 }; pub fn main() !void { diff --git a/course/code/16/switch.zig b/course/code/16/switch.zig index 98bad14b..1e2fcfca 100644 --- a/course/code/16/switch.zig +++ b/course/code/16/switch.zig @@ -186,7 +186,7 @@ fn getNum(u: U) u32 { // 而 tag 则是对应的标签名,这是编译期可知的 inline else => |num, tag| { if (tag == .b) { - return @intFromFloat(num); + return @trunc(num); } return num; }, diff --git a/course/code/release b/course/code/release deleted file mode 120000 index c3e11da9..00000000 --- a/course/code/release +++ /dev/null @@ -1 +0,0 @@ -./16 \ No newline at end of file diff --git a/course/code/release/array.zig b/course/code/release/array.zig new file mode 100644 index 00000000..06c48ccb --- /dev/null +++ b/course/code/release/array.zig @@ -0,0 +1,136 @@ +pub fn main() !void { + CreateArray.main(); + Deconstruct.main(); + Matrix.main(); + TerminatedArray.main(); + Multiply.main(); + Connect.main(); + FuncInitArray.main(); + ComptimeInitArray.main(); +} +const CreateArray = struct { + // #region create_array + const print = @import("std").debug.print; + + pub fn main() void { + const message = [5]u8{ 'h', 'e', 'l', 'l', 'o' }; + // const message = [_]u8{ 'h', 'e', 'l', 'l', 'o' }; + print("{s}\n", .{message}); // hello + print("{c}\n", .{message[0]}); // h + } + // #endregion create_array +}; + +const Deconstruct = struct { + // #region deconstruct + const print = @import("std").debug.print; + + fn swizzleRgbaToBgra(rgba: [4]u8) [4]u8 { + // 解构 + const r, const g, const b, const a = rgba; + return .{ b, g, r, a }; + } + + pub fn main() void { + const pos = [_]i32{ 1, 2 }; + // 解构 + const x, const y = pos; + print("x = {}, y = {}\n", .{ x, y }); + + const orange: [4]u8 = .{ 255, 165, 0, 255 }; + print("{any}\n", .{swizzleRgbaToBgra(orange)}); + } + // #endregion deconstruct +}; + +const Matrix = struct { + // #region matrix + const print = @import("std").debug.print; + + pub fn main() void { + const matrix_4x4 = [4][4]f32{ + [_]f32{ 1.0, 0.0, 0.0, 0.0 }, + [_]f32{ 0.0, 1.0, 0.0, 1.0 }, + [_]f32{ 0.0, 0.0, 1.0, 0.0 }, + [_]f32{ 0.0, 0.0, 0.0, 1.0 }, + }; + + for (matrix_4x4, 0..) |arr_val, arr_index| { + for (arr_val, 0..) |val, index| { + print("元素{}-{}是: {}\n", .{ arr_index, index, val }); + } + } + } + // #endregion matrix +}; + +const TerminatedArray = struct { + // #region terminated_array + const print = @import("std").debug.print; + + pub fn main() void { + const array = [_:0]u8{ 1, 2, 3, 4 }; + print("数组长度为: {}\n", .{array.len}); // 4 + print("数组最后一个元素值: {}\n", .{array[array.len - 1]}); // 4 + print("哨兵值为: {}\n", .{array[array.len]}); // 0 + } + // #endregion terminated_array +}; + +const Multiply = struct { + // #region multiply + const print = @import("std").debug.print; + + pub fn main() void { + const small = [3]i8{ 1, 2, 3 }; + const big: [9]i8 = small ** 3; + print("{any}\n", .{big}); // [9]i8{ 1, 2, 3, 1, 2, 3, 1, 2, 3 } + } + // #endregion multiply +}; + +const Connect = struct { + // #region connect + const print = @import("std").debug.print; + + pub fn main() void { + const part_one = [_]i32{ 1, 2, 3, 4 }; + const part_two = [_]i32{ 5, 6, 7, 8 }; + const all_of_it = part_one ++ part_two; // [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 } + + _ = all_of_it; + } + // #endregion connect +}; + +const FuncInitArray = struct { + // #region func_init_array + const print = @import("std").debug.print; + + pub fn main() void { + const array = [_]i32{make(3)} ** 10; + print("{any}\n", .{array}); + } + + fn make(x: i32) i32 { + return x + 1; + } + // #endregion func_init_array +}; + +const ComptimeInitArray = struct { + // #region comptime_init_array + const print = @import("std").debug.print; + + pub fn main() void { + const fancy_array = init: { + var initial_value: [10]usize = undefined; + for (&initial_value, 0..) |*pt, i| { + pt.* = i; + } + break :init initial_value; + }; + print("{any}\n", .{fancy_array}); + } + // #endregion comptime_init_array +}; diff --git a/course/code/release/assembly.zig b/course/code/release/assembly.zig new file mode 100644 index 00000000..d6efe2ef --- /dev/null +++ b/course/code/release/assembly.zig @@ -0,0 +1,69 @@ +pub fn main() !void {} + +const external_assembly = struct { + // #region external_assembly + const std = @import("std"); + + comptime { + asm ( + \\.global my_func; + \\.type my_func, @function; + \\my_func: + \\ lea (%rdi,%rsi,1),%eax + \\ retq + ); + } + + extern fn my_func(a: i32, b: i32) i32; + + pub fn main() void { + std.debug.print("{}\n", .{my_func(2, 5)}); + } + // #endregion external_assembly +}; + +const inline_assembly = struct { + // #region inline_assembly + const std = @import("std"); + + pub fn main() noreturn { + // Temporarily disabled due to Zig 0.15 syntax changes + // const msg = "hello world\n"; + // _ = syscall3(SYS_write, STDOUT_FILENO, @intFromPtr(msg), msg.len); + // _ = syscall1(SYS_exit, 0); + std.process.exit(0); + } + + pub const SYS_write = 1; + pub const SYS_exit = 60; + + pub const STDOUT_FILENO = 1; + + // Temporarily disabled due to Zig 0.15 inline assembly syntax changes + // TODO: Update to new Zig 0.15 inline assembly syntax + + // pub fn syscall1(number: usize, arg1: usize) usize { + // var result: usize = undefined; + // asm volatile ("syscall" + // : [ret] "={rax}" (result) + // : [number] "{rax}" (number), + // [arg1] "{rdi}" (arg1) + // : "rcx", "r11" + // ); + // return result; + // } + + // pub fn syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) usize { + // var result: usize = undefined; + // asm volatile ("syscall" + // : [ret] "={rax}" (result) + // : [number] "{rax}" (number), + // [arg1] "{rdi}" (arg1), + // [arg2] "{rsi}" (arg2), + // [arg3] "{rdx}" (arg3) + // : "rcx", "r11" + // ); + // return result; + // } + // #endregion inline_assembly +}; diff --git a/course/code/release/atomic.zig b/course/code/release/atomic.zig new file mode 100644 index 00000000..acdc0712 --- /dev/null +++ b/course/code/release/atomic.zig @@ -0,0 +1,50 @@ +pub fn main() !void { + // #region atomic_value + const std = @import("std"); + const RefCount = struct { + count: std.atomic.Value(usize), + dropFn: *const fn (*RefCount) void, + + const RefCount = @This(); + + fn ref(rc: *RefCount) void { + // no synchronization necessary; just updating a counter. + _ = rc.count.fetchAdd(1, .monotonic); + } + + fn unref(rc: *RefCount) void { + // release ensures code before unref() happens-before the + // count is decremented as dropFn could be called by then. + if (rc.count.fetchSub(1, .release) == 1) { + // seeing 1 in the counter means that other unref()s have happened, + // but it doesn't mean that uses before each unref() are visible. + // The load acquires the release-sequence created by previous unref()s + // in order to ensure visibility of uses before dropping. + _ = rc.count.load(.acquire); + (rc.dropFn)(rc); + } + } + + fn noop(rc: *RefCount) void { + _ = rc; + } + }; + + var ref_count: RefCount = .{ + .count = std.atomic.Value(usize).init(0), + .dropFn = RefCount.noop, + }; + ref_count.ref(); + ref_count.unref(); + // #endregion atomic_value + +} + +test "spinLoopHint" { + const std = @import("std"); + // #region spinLoopHint + for (0..10) |_| { + std.atomic.spinLoopHint(); + } + // #endregion spinLoopHint +} diff --git a/course/code/release/build_system/README.md b/course/code/release/build_system/README.md new file mode 100644 index 00000000..4aaf9c5f --- /dev/null +++ b/course/code/release/build_system/README.md @@ -0,0 +1 @@ +该文件夹是构建系统的示例文件! diff --git a/course/code/release/build_system/basic/build.zig b/course/code/release/build_system/basic/build.zig new file mode 100644 index 00000000..69ae8244 --- /dev/null +++ b/course/code/release/build_system/basic/build.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "zig", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + b.installArtifact(exe); +} diff --git a/course/code/release/build_system/basic/build.zig.zon b/course/code/release/build_system/basic/build.zig.zon new file mode 100644 index 00000000..8553e004 --- /dev/null +++ b/course/code/release/build_system/basic/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .basic, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0x907975534fe79435, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/build_system/basic/src/main.zig b/course/code/release/build_system/basic/src/main.zig new file mode 100644 index 00000000..bf45938f --- /dev/null +++ b/course/code/release/build_system/basic/src/main.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + const io = init.io; + // `std.debug.print` 会输出到标准错误。 + std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + + // stdout is for the actual output of your application, for example if you + // are implementing gzip, then only the compressed bytes should be sent to + // stdout, not any debugging messages. + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); + const stdout = &stdout_writer.interface; + + try stdout.print("Run `zig build test` to run the tests.\n", .{}); + + try stdout.flush(); +} + +test "simple test" { + var list = std.ArrayList(i32).init(std.testing.allocator); + defer list.deinit(); // try commenting this out and see if zig detects the memory leak! + try list.append(42); + try std.testing.expectEqual(@as(i32, 42), list.pop()); +} diff --git a/course/code/release/build_system/build.zig b/course/code/release/build_system/build.zig new file mode 100644 index 00000000..2cc7cd76 --- /dev/null +++ b/course/code/release/build_system/build.zig @@ -0,0 +1,88 @@ +const std = @import("std"); +const args = [_][]const u8{ "zig", "build" }; + +pub fn build(b: *std.Build) !void { + const optimize = b.standardOptimizeOption(.{}); + const io = b.graph.io; + // #region crossTarget + // 构建一个target + const target_query = std.Target.Query{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, + }; + + const ResolvedTarget = std.Build.ResolvedTarget; + + // 解析的target + const resolved_target: ResolvedTarget = b.resolveTargetQuery(target_query); + + // 解析结果 + const target: std.Target = resolved_target.result; + _ = target; + + // 构建 exe + const exe = b.addExecutable(.{ + .name = "zig", + .root_module = b.addModule("zig", .{ + .root_source_file = b.path("main.zig"), + // 实际使用的是resolved_target + .target = resolved_target, + .optimize = optimize, + }), + }); + // #endregion crossTarget + + b.installArtifact(exe); + + const full_path = try std.process.currentPathAlloc(io, b.allocator); + defer b.allocator.free(full_path); + + var dir = std.Io.Dir.openDirAbsolute(io, full_path, .{ .iterate = true }) catch |err| { + std.log.err("open path failed {s}, err is {}", .{ full_path, err }); + std.process.exit(1); + }; + defer dir.close(io); + + var iterate = dir.iterate(); + + while (iterate.next(io) catch |err| { + std.log.err("iterate examples_path failed, err is {}", .{err}); + std.process.exit(1); + }) |entry| { + // get the entry name, entry can be file or directory + const name = entry.name; + if (entry.kind == .directory) { + if (eqlu8(name, ".zig-cache") or eqlu8(name, "zig-out") or eqlu8(name, "zig-cache")) + continue; + + // build cwd + const cwd = std.fs.path.join(b.allocator, &[_][]const u8{ + full_path, + name, + }) catch |err| { + std.log.err("fmt path failed, err is {}", .{err}); + std.process.exit(1); + }; + + // open entry dir + const entry_dir = std.Io.Dir.openDirAbsolute(io, cwd, .{}) catch unreachable; + defer entry_dir.close(io); + + entry_dir.access(io, "build.zig", .{}) catch { + std.log.err("not found build.zig in path {s}", .{cwd}); + std.process.exit(1); + }; + + var child = std.process.spawn(io, .{ + .argv = &args, + .cwd = .{ .path = cwd }, + }) catch unreachable; + _ = child.wait(io) catch unreachable; + } + } +} + +fn eqlu8(a: []const u8, b: []const u8) bool { + return std.mem.eql(u8, a, b); +} diff --git a/course/code/release/build_system/cli/build.zig b/course/code/release/build_system/cli/build.zig new file mode 100644 index 00000000..a1036af5 --- /dev/null +++ b/course/code/release/build_system/cli/build.zig @@ -0,0 +1,21 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const is_strip = + b.option(bool, "is_strip", "whether strip executable") orelse + false; + + const exe = b.addExecutable(.{ + .name = "zig", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .strip = is_strip, + }), + }); + + b.installArtifact(exe); +} diff --git a/course/code/release/build_system/cli/build.zig.zon b/course/code/release/build_system/cli/build.zig.zon new file mode 100644 index 00000000..09eef71d --- /dev/null +++ b/course/code/release/build_system/cli/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .cli, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0x48f6513c15de5e44, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/build_system/cli/src/main.zig b/course/code/release/build_system/cli/src/main.zig new file mode 100644 index 00000000..947ed515 --- /dev/null +++ b/course/code/release/build_system/cli/src/main.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + const io = init.io; + // `std.debug.print` 会输出到标准错误。 + std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + + // stdout is for the actual output of your application, for example if you + // are implementing gzip, then only the compressed bytes should be sent to + // stdout, not any debugging messages. + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); + const stdout = &stdout_writer.interface; + + try stdout.print("Run `zig build test` to run the tests.\n", .{}); + + try stdout.flush(); // don't forget to flush! +} + +test "simple test" { + var list = std.ArrayList(i32).init(std.testing.allocator); + defer list.deinit(); // try commenting this out and see if zig detects the memory leak! + try list.append(42); + try std.testing.expectEqual(@as(i32, 42), list.pop()); +} diff --git a/course/code/release/build_system/docs/build.zig b/course/code/release/build_system/docs/build.zig new file mode 100644 index 00000000..3ab94831 --- /dev/null +++ b/course/code/release/build_system/docs/build.zig @@ -0,0 +1,32 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // 标准构建目标 + const target = b.standardTargetOptions(.{}); + // 标准构建模式 + const optimize = b.standardOptimizeOption(.{}); + + // 构建一个 object,用于生成文档 + const object = b.addObject(.{ + .name = "object", + .root_module = b.addModule("object", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }), + }); + // 创建一个 step + const docs_step = b.step("docs", "Generate docs"); + + // 生成文档 + const docs_install = b.addInstallDirectory(.{ + // 指定文档来源 + .source_dir = object.getEmittedDocs(), + // 指定安装目录 + .install_dir = .prefix, + // 指定文档子文件夹 + .install_subdir = "docs", + }); + + docs_step.dependOn(&docs_install.step); +} diff --git a/course/code/release/build_system/docs/build.zig.zon b/course/code/release/build_system/docs/build.zig.zon new file mode 100644 index 00000000..a40db6b3 --- /dev/null +++ b/course/code/release/build_system/docs/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .docs, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0x51572bb73db54779, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/build_system/docs/src/root.zig b/course/code/release/build_system/docs/src/root.zig new file mode 100644 index 00000000..ecfeade1 --- /dev/null +++ b/course/code/release/build_system/docs/src/root.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const testing = std.testing; + +export fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "basic add functionality" { + try testing.expect(add(3, 7) == 10); +} diff --git a/course/code/release/build_system/embedfile/build.zig b/course/code/release/build_system/embedfile/build.zig new file mode 100644 index 00000000..44ba9a52 --- /dev/null +++ b/course/code/release/build_system/embedfile/build.zig @@ -0,0 +1,44 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // 标准构建目标 + const target = b.standardTargetOptions(.{}); + + // 标准构建模式 + const optimize = b.standardOptimizeOption(.{}); + + // 添加一个二进制可执行程序构建 + const exe = b.addExecutable(.{ + .name = "zig", + .root_module = b.addModule("zig", .{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + exe.root_module.addAnonymousImport( + "hello", + .{ .root_source_file = b.path("src/hello.txt") }, + ); + + // 添加到顶级 install step 中作为依赖 + b.installArtifact(exe); + + // zig 提供了一个方便的函数允许我们直接运行构建结果 + const run_cmd = b.addRunArtifact(exe); + + // 指定依赖 + run_cmd.step.dependOn(b.getInstallStep()); + + // 传递参数 + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // 指定一个 step 为 run + const run_step = b.step("run", "Run the app"); + + // 指定该 step 依赖于 run_exe,即实际的运行 + run_step.dependOn(&run_cmd.step); +} diff --git a/course/code/release/build_system/embedfile/build.zig.zon b/course/code/release/build_system/embedfile/build.zig.zon new file mode 100644 index 00000000..9fab9793 --- /dev/null +++ b/course/code/release/build_system/embedfile/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .embedfile, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0x3e6a9e8b250bb664, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/build_system/embedfile/src/hello.txt b/course/code/release/build_system/embedfile/src/hello.txt new file mode 100644 index 00000000..b45ef6fe --- /dev/null +++ b/course/code/release/build_system/embedfile/src/hello.txt @@ -0,0 +1 @@ +Hello, World! \ No newline at end of file diff --git a/course/code/release/build_system/embedfile/src/main.zig b/course/code/release/build_system/embedfile/src/main.zig new file mode 100644 index 00000000..467b20ed --- /dev/null +++ b/course/code/release/build_system/embedfile/src/main.zig @@ -0,0 +1,7 @@ +const std = @import("std"); +const hello = @embedFile("hello"); +// const hello = @embedFile("hello.txt"); 均可以 + +pub fn main() !void { + std.debug.print("{s}\n", .{hello}); +} diff --git a/course/code/release/build_system/externalfile/build.zig b/course/code/release/build_system/externalfile/build.zig new file mode 100644 index 00000000..e1c6dcd9 --- /dev/null +++ b/course/code/release/build_system/externalfile/build.zig @@ -0,0 +1,63 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + // 标准构建目标 + const target = b.standardTargetOptions(.{}); + + // 标准构建模式 + const optimize = b.standardOptimizeOption(.{}); + + // 在 windows 平台无法使用 bash,故我们直接返回 + if (target.result.os.tag == .windows) { + return; + } + + // 添加一个二进制可执行程序构建 + const exe = b.addExecutable(.{ + .name = "zig", + .root_module = b.addModule("zig", .{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + // 构建一个运行命令 + const run_sys_cmd = b.addSystemCommand(&.{ + "/bin/sh", + "-c", + }); + + // 添加参数,此方法允许添加多个参数 + // 也可以使用 addArg 来添加单个参数 + run_sys_cmd.addArgs(&.{ + "echo hello", + }); + + // 尝试运行命令并捕获标准输出 + // 也可以使用 captureStdErr 来捕获标准错误输出 + const output = run_sys_cmd.captureStdOut(.{}); + + // 添加一个匿名的依赖 + exe.root_module.addAnonymousImport("hello", .{ .root_source_file = output }); + + // 添加到顶级 install step 中作为依赖 + b.installArtifact(exe); + + // zig 提供了一个方便的函数允许我们直接运行构建结果 + const run_cmd = b.addRunArtifact(exe); + + // 指定依赖 + run_cmd.step.dependOn(b.getInstallStep()); + + // 传递参数 + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // 指定一个 step 为 run + const run_step = b.step("run", "Run the app"); + + // 指定该 step 依赖于 run_exe,即实际的运行 + run_step.dependOn(&run_cmd.step); +} diff --git a/course/code/release/build_system/externalfile/build.zig.zon b/course/code/release/build_system/externalfile/build.zig.zon new file mode 100644 index 00000000..bded66c1 --- /dev/null +++ b/course/code/release/build_system/externalfile/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .externalfile, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0x61de81227ca0d23c, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/build_system/externalfile/src/main.zig b/course/code/release/build_system/externalfile/src/main.zig new file mode 100644 index 00000000..4cab998e --- /dev/null +++ b/course/code/release/build_system/externalfile/src/main.zig @@ -0,0 +1,6 @@ +const std = @import("std"); +const hello = @embedFile("hello"); + +pub fn main() !void { + std.debug.print("{s}", .{hello}); +} diff --git a/course/code/release/build_system/lib/build.zig b/course/code/release/build_system/lib/build.zig new file mode 100644 index 00000000..2d0b4020 --- /dev/null +++ b/course/code/release/build_system/lib/build.zig @@ -0,0 +1,31 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // 使用默认提供的构建目标,支持我们从命令行构建时指定构建目标(架构、系统、abi等等) + const target = b.standardTargetOptions(.{}); + + // 使用默认提供的优化方案,支持我们从命令行构建时指定构建模式 + const optimize = b.standardOptimizeOption(.{}); + + // 尝试添加一个静态库 + const lib = b.addLibrary(.{ .name = "example", .root_module = b.createModule(.{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }) }); + + b.installArtifact(lib); + + const exe = b.addExecutable(.{ + .name = "zig", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + exe.root_module.linkLibrary(lib); + + b.installArtifact(exe); +} diff --git a/course/code/release/build_system/lib/build.zig.zon b/course/code/release/build_system/lib/build.zig.zon new file mode 100644 index 00000000..6ca3a749 --- /dev/null +++ b/course/code/release/build_system/lib/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .library, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0xa18098bc77aad8e8, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/build_system/lib/src/main.zig b/course/code/release/build_system/lib/src/main.zig new file mode 100644 index 00000000..34185e55 --- /dev/null +++ b/course/code/release/build_system/lib/src/main.zig @@ -0,0 +1,18 @@ +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + const io = init.io; + // `std.debug.print` 会输出到标准错误。 + std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + + // stdout is for the actual output of your application, for example if you + // are implementing gzip, then only the compressed bytes should be sent to + // stdout, not any debugging messages. + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); + const stdout = &stdout_writer.interface; + + try stdout.print("Run `zig build test` to run the tests.\n", .{}); + + try stdout.flush(); +} diff --git a/course/code/release/build_system/lib/src/root.zig b/course/code/release/build_system/lib/src/root.zig new file mode 100644 index 00000000..ecfeade1 --- /dev/null +++ b/course/code/release/build_system/lib/src/root.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const testing = std.testing; + +export fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "basic add functionality" { + try testing.expect(add(3, 7) == 10); +} diff --git a/course/code/release/build_system/main.zig b/course/code/release/build_system/main.zig new file mode 100644 index 00000000..b16fb0fd --- /dev/null +++ b/course/code/release/build_system/main.zig @@ -0,0 +1,5 @@ +const std = @import("std"); + +pub fn main() !void { + std.debug.print("Hello, World!", .{}); +} diff --git a/course/code/release/build_system/options/build.zig b/course/code/release/build_system/options/build.zig new file mode 100644 index 00000000..dc5af270 --- /dev/null +++ b/course/code/release/build_system/options/build.zig @@ -0,0 +1,35 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // 标准构建目标 + const target = b.standardTargetOptions(.{}); + + // 标准构建模式 + const optimize = b.standardOptimizeOption(.{}); + + // 添加一个二进制可执行程序构建 + const exe = b.addExecutable(.{ + .name = "zig", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + // 获取一个简单的时间值用于演示 options 功能 + // 注意:Zig 0.16 移除了 std.time.timestamp(),这里使用示例值 + const timestamp: i64 = 1700000000; // 示例时间戳 + + // 创建一个 options + const options = b.addOptions(); + + // 向 options 添加 option, 变量名是time_stamp + options.addOption(i64, "time_stamp", timestamp); + + // 向 exe 中添加 options + exe.root_module.addOptions("timestamp", options); + + // 添加到顶级 install step 中作为依赖 + b.installArtifact(exe); +} diff --git a/course/code/release/build_system/options/build.zig.zon b/course/code/release/build_system/options/build.zig.zon new file mode 100644 index 00000000..b3a7f357 --- /dev/null +++ b/course/code/release/build_system/options/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .options, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0xd035fa8769f41b1a, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/build_system/options/src/main.zig b/course/code/release/build_system/options/src/main.zig new file mode 100644 index 00000000..635449e3 --- /dev/null +++ b/course/code/release/build_system/options/src/main.zig @@ -0,0 +1,7 @@ +const std = @import("std"); +// timestamp 这个包是通过 build.zig 添加的 +const timestamp = @import("timestamp"); + +pub fn main() !void { + std.debug.print("build time stamp is {}\n", .{timestamp.time_stamp}); +} diff --git a/course/code/release/build_system/step/build.zig b/course/code/release/build_system/step/build.zig new file mode 100644 index 00000000..6e40f3b4 --- /dev/null +++ b/course/code/release/build_system/step/build.zig @@ -0,0 +1,44 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // 标准构建目标 + const target = b.standardTargetOptions(.{}); + + // 标准构建模式 + const optimize = b.standardOptimizeOption(.{}); + + // 添加一个二进制可执行程序构建 + const exe = b.addExecutable(.{ + .name = "hello", + .root_module = b.addModule("hello", .{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + // 添加到顶级 install step 中作为依赖 + b.installArtifact(exe); + + // zig 提供了一个方便的函数允许我们直接运行构建结果 + const run_exe = b.addRunArtifact(exe); + + // 注意:该步骤可选,显式声明运行依赖于构建 + // 这会使运行是从构建输出目录(默认为 zig-out/bin )运行而不是构建缓存中运行 + // 不过,如果应用程序运行依赖于其他已存在的文件(例如某些 ini 配置文件) + // 这可以确保它们正确的运行 + run_exe.step.dependOn(b.getInstallStep()); + + // 注意:此步骤可选 + // 此操作允许用户通过构建系统的命令传递参数,例如 zig build -- arg1 arg2 + // 当前是将参数传递给运行构建结果 + if (b.args) |args| { + run_exe.addArgs(args); + } + + // 指定一个 step 为 run + const run_step = b.step("run", "Run the application"); + + // 指定该 step 依赖于 run_exe,即实际的运行 + run_step.dependOn(&run_exe.step); +} diff --git a/course/code/release/build_system/step/build.zig.zon b/course/code/release/build_system/step/build.zig.zon new file mode 100644 index 00000000..a2461217 --- /dev/null +++ b/course/code/release/build_system/step/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .step, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0x43b9fe3c8067cab6, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/build_system/step/src/main.zig b/course/code/release/build_system/step/src/main.zig new file mode 100644 index 00000000..34185e55 --- /dev/null +++ b/course/code/release/build_system/step/src/main.zig @@ -0,0 +1,18 @@ +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + const io = init.io; + // `std.debug.print` 会输出到标准错误。 + std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + + // stdout is for the actual output of your application, for example if you + // are implementing gzip, then only the compressed bytes should be sent to + // stdout, not any debugging messages. + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); + const stdout = &stdout_writer.interface; + + try stdout.print("Run `zig build test` to run the tests.\n", .{}); + + try stdout.flush(); +} diff --git a/course/code/release/build_system/system_lib/build.zig b/course/code/release/build_system/system_lib/build.zig new file mode 100644 index 00000000..55ef0097 --- /dev/null +++ b/course/code/release/build_system/system_lib/build.zig @@ -0,0 +1,32 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // 使用默认提供的构建目标,支持我们从命令行构建时指定构建目标(架构、系统、abi等等) + const target = b.standardTargetOptions(.{}); + + // 使用默认提供的优化方案,支持我们从命令行构建时指定构建模式 + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "zip", + .root_module = b.addModule("zip", .{ + .root_source_file = b.path("src/main.zig"), + // 构建目标 + .target = target, + // 构建模式 + .optimize = optimize, + }), + }); + + if (target.result.os.tag == .windows) + // 连接到系统的 ole32 + exe.root_module.linkSystemLibrary("ole32", .{}) + else + // 链接到系统的 libz + exe.root_module.linkSystemLibrary("z", .{}); + + // 链接到 libc + exe.root_module.linkSystemLibrary("c", .{}); + + b.installArtifact(exe); +} diff --git a/course/code/release/build_system/system_lib/build.zig.zon b/course/code/release/build_system/system_lib/build.zig.zon new file mode 100644 index 00000000..6f9b6047 --- /dev/null +++ b/course/code/release/build_system/system_lib/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .system_lib, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0x64b791172db5c549, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/build_system/system_lib/src/main.zig b/course/code/release/build_system/system_lib/src/main.zig new file mode 100644 index 00000000..1df86419 --- /dev/null +++ b/course/code/release/build_system/system_lib/src/main.zig @@ -0,0 +1,5 @@ +const std = @import("std"); + +pub fn main() !void { + std.log.info("Hello, world!", .{}); +} diff --git a/course/code/release/build_system/test/build.zig b/course/code/release/build_system/test/build.zig new file mode 100644 index 00000000..48ca138a --- /dev/null +++ b/course/code/release/build_system/test/build.zig @@ -0,0 +1,46 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // 标准构建目标 + const target = b.standardTargetOptions(.{}); + + // 标准构建模式 + const optimize = b.standardOptimizeOption(.{}); + + // 添加一个二进制可执行程序构建 + const exe = b.addExecutable(.{ + .name = "zig", + .root_module = b.addModule("zig", .{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + // 添加到顶级 install step 中作为依赖 + b.installArtifact(exe); + + // 此处开始构建单元测试 + + // 构建一个单元测试的 Compile + const exe_unit_tests = b.addTest(.{ + .root_module = b.addModule("zig_unit_tests", .{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + // 执行单元测试 + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + + // 如果想要跳过外部来自于其他包的单元测试(例如依赖中的包) + // 可以使用 skip_foreign_checks + run_exe_unit_tests.skip_foreign_checks = true; + + // 构建一个 step,用于执行测试 + const test_step = b.step("test", "Run unit tests"); + + // 测试 step 依赖上方构建的 run_exe_unit_tests + test_step.dependOn(&run_exe_unit_tests.step); +} diff --git a/course/code/release/build_system/test/build.zig.zon b/course/code/release/build_system/test/build.zig.zon new file mode 100644 index 00000000..92b35441 --- /dev/null +++ b/course/code/release/build_system/test/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .ttest, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0x334b1002be4b456c, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/build_system/test/src/main.zig b/course/code/release/build_system/test/src/main.zig new file mode 100644 index 00000000..bf45938f --- /dev/null +++ b/course/code/release/build_system/test/src/main.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + const io = init.io; + // `std.debug.print` 会输出到标准错误。 + std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + + // stdout is for the actual output of your application, for example if you + // are implementing gzip, then only the compressed bytes should be sent to + // stdout, not any debugging messages. + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); + const stdout = &stdout_writer.interface; + + try stdout.print("Run `zig build test` to run the tests.\n", .{}); + + try stdout.flush(); +} + +test "simple test" { + var list = std.ArrayList(i32).init(std.testing.allocator); + defer list.deinit(); // try commenting this out and see if zig detects the memory leak! + try list.append(42); + try std.testing.expectEqual(@as(i32, 42), list.pop()); +} diff --git a/course/code/release/build_system/test/src/root.zig b/course/code/release/build_system/test/src/root.zig new file mode 100644 index 00000000..ecfeade1 --- /dev/null +++ b/course/code/release/build_system/test/src/root.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const testing = std.testing; + +export fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "basic add functionality" { + try testing.expect(add(3, 7) == 10); +} diff --git a/course/code/release/build_system/tinytetris/build.zig b/course/code/release/build_system/tinytetris/build.zig new file mode 100644 index 00000000..dd977698 --- /dev/null +++ b/course/code/release/build_system/tinytetris/build.zig @@ -0,0 +1,59 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // 构建目标 + const target = b.standardTargetOptions(.{}); + + // 构建优化模式 + const optimize = b.standardOptimizeOption(.{}); + + if (target.result.os.tag == .windows) { + return; + } + + // 添加一个二进制可执行程序构建 + // 注意:我们在这里并没有使用 root_source_file 字段 + // 该字段是为 zig 源文件准备的 + const exe = b.addExecutable(.{ + .name = "tinytetris", + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + }), + }); + + // 添加 C 源代码文件,两个参数: + // 源代码路径(相对于build.zig) + // 传递的 flags + // 多个 C 源代码文件可以使用 addCSourceFiles + exe.root_module.addCSourceFile(.{ + .file = b.path("src/main.cc"), + .flags = &.{}, + }); + + // 链接 C++ 标准库 + // 同理对于 C 标准库可以使用 `exe.root_module.linkSystemLibrary("c", .{})` + exe.root_module.linkSystemLibrary("c++", .{}); + + // 链接系统库 ncurses + exe.root_module.linkSystemLibrary("ncurses", .{}); + + // 添加到顶级 install step 中作为依赖 + b.installArtifact(exe); + + // 创建一个运行 + const run_cmd = b.addRunArtifact(exe); + + // 依赖于构建 + run_cmd.step.dependOn(b.getInstallStep()); + + // 运行时参数传递 + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // 运行的 step + const run_step = b.step("run", "Run the app"); + // 依赖于前面的运行 + run_step.dependOn(&run_cmd.step); +} diff --git a/course/code/release/build_system/tinytetris/build.zig.zon b/course/code/release/build_system/tinytetris/build.zig.zon new file mode 100644 index 00000000..a8f670af --- /dev/null +++ b/course/code/release/build_system/tinytetris/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .tinytetris, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0x5f03916d4d995c27, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/build_system/tinytetris/src/main.cc b/course/code/release/build_system/tinytetris/src/main.cc new file mode 100644 index 00000000..3018b6a4 --- /dev/null +++ b/course/code/release/build_system/tinytetris/src/main.cc @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include + +// block layout is: {w-1,h-1}{x0,y0}{x1,y1}{x2,y2}{x3,y3} (two bits each) +int x = 431424, y = 598356, r = 427089, px = 247872, py = 799248, pr, + c = 348480, p = 615696, tick, board[20][10], + block[7][4] = {{x, y, x, y}, + {r, p, r, p}, + {c, c, c, c}, + {599636, 431376, 598336, 432192}, + {411985, 610832, 415808, 595540}, + {px, py, px, py}, + {614928, 399424, 615744, 428369}}, + score = 0; + +// extract a 2-bit number from a block entry +int NUM(int x, int y) { return 3 & block[p][x] >> y; } + +// create a new piece, don't remove old one (it has landed and should stick) +void new_piece() { + y = py = 0; + p = rand() % 7; + r = pr = rand() % 4; + x = px = rand() % (10 - NUM(r, 16)); +} + +// draw the board and score +void frame() { + for (int i = 0; i < 20; i++) { + move(1 + i, 1); // otherwise the box won't draw + for (int j = 0; j < 10; j++) { + board[i][j] && attron(262176 | board[i][j] << 8); + printw(" "); + attroff(262176 | board[i][j] << 8); + } + } + move(21, 1); + printw("Score: %d", score); + refresh(); +} + +// set the value of the board for a particular (x,y,r) piece +void set_piece(int x, int y, int r, int v) { + for (int i = 0; i < 8; i += 2) { + board[NUM(r, i * 2) + y][NUM(r, (i * 2) + 2) + x] = v; + } +} + +// move a piece from old (p*) coords to new +void update_piece() { + set_piece(px, py, pr, 0); + set_piece(px = x, py = y, pr = r, p + 1); +} + +// remove line(s) from the board if they're full +void remove_line() { + for (int row = y; row <= y + NUM(r, 18); row++) { + c = 1; + for (int i = 0; i < 10; i++) { + c *= board[row][i]; + } + if (!c) { + continue; + } + for (int i = row - 1; i > 0; i--) { + memcpy(&board[i + 1][0], &board[i][0], 40); + } + memset(&board[0][0], 0, 10); + score++; + } +} + +// check if placing p at (x,y,r) will be a collision +int check_hit(int x, int y, int r) { + if (y + NUM(r, 18) > 19) { + return 1; + } + set_piece(px, py, pr, 0); + c = 0; + for (int i = 0; i < 8; i += 2) { + board[y + NUM(r, i * 2)][x + NUM(r, (i * 2) + 2)] && c++; + } + set_piece(px, py, pr, p + 1); + return c; +} + +// slowly tick the piece y position down so the piece falls +int do_tick() { + if (++tick > 30) { + tick = 0; + if (check_hit(x, y + 1, r)) { + if (!y) { + return 0; + } + remove_line(); + new_piece(); + } else { + y++; + update_piece(); + } + } + return 1; +} + +// main game loop with wasd input checking +void runloop() { + while (do_tick()) { + usleep(10000); + if ((c = getch()) == 'a' && x > 0 && !check_hit(x - 1, y, r)) { + x--; + } + if (c == 'd' && x + NUM(r, 16) < 9 && !check_hit(x + 1, y, r)) { + x++; + } + if (c == 's') { + while (!check_hit(x, y + 1, r)) { + y++; + update_piece(); + } + remove_line(); + new_piece(); + } + if (c == 'w') { + ++r %= 4; + while (x + NUM(r, 16) > 9) { + x--; + } + if (check_hit(x, y, r)) { + x = px; + r = pr; + } + } + if (c == 'q') { + return; + } + update_piece(); + frame(); + } +} + +// init curses and start runloop +int main() { + srand(time(0)); + initscr(); + start_color(); + // colours indexed by their position in the block + for (int i = 1; i < 8; i++) { + init_pair(i, i, 0); + } + new_piece(); + resizeterm(22, 22); + noecho(); + timeout(0); + curs_set(0); + box(stdscr, 0, 0); + runloop(); + endwin(); +} \ No newline at end of file diff --git a/course/code/release/char-and-boolean.zig b/course/code/release/char-and-boolean.zig new file mode 100644 index 00000000..1c88251b --- /dev/null +++ b/course/code/release/char-and-boolean.zig @@ -0,0 +1,61 @@ +pub fn main() !void { + try CHAR.main(); +} + +const CHAR = struct { + const print = @import("std").debug.print; + const expect = @import("std").testing.expect; + + pub fn main() !void { + // #region char + // 格式化时,可以使用 u 输出对应的字符 + const me_zh = '我'; + print("{0u} = {0x}\n", .{me_zh}); // 我 = 6211 + + // 如果是 ASCII 字符,还可以使用 c 进行格式化 + const me_en = 'I'; + print("{0u} = {0c} = {0x}\n", .{me_en}); // I = I = 49 + + // 下面的写法会报错,因为这些 emoji 虽然看上去只有一个字,但其实需要由多个码位组合而成 + // const hand = '🖐🏽'; + // const flag = '🇨🇳'; + // #endregion char + + // #region string-literal + // 存储的是 UTF-8 编码序列 + const bytes = "Hello, 世界!"; + + print("{}\n", .{@TypeOf(bytes)}); // *const [16:0]u8 + print("{}\n", .{bytes.len}); // 16 + + // 通过索引访问到的是 UTF-8 编码序列中的字节 + // 由于 UTF-8 兼容 ASCII,所以可以直接打印 ASCII 字符 + print("{c}\n", .{bytes[1]}); // 'e' + + // “世”字的 UTF-8 编码为 E4 B8 96 + try expect(bytes[7] == 0xE4); + try expect(bytes[8] == 0xB8); + try expect(bytes[9] == 0x96); + + // 以 NUL 结尾 + print("{d}\n", .{bytes[16]}); // 0 + + // #endregion string-literal + + // #region multiline-string-literal + // “我”字的 UTF-8 编码为 E6 88 91 + const string = + \\I + \\我 + ; + try expect(string[0] == 'I'); + try expect(string[1] == '\n'); + try expect(string[2] == 0xE6); + try expect(string[3] == 0x88); + try expect(string[4] == 0x91); + try expect(string[5] == 0); + try expect(string.len == 5); + + // #endregion multiline-string-literal + } +}; diff --git a/course/code/release/comptime.zig b/course/code/release/comptime.zig new file mode 100644 index 00000000..3c14639a --- /dev/null +++ b/course/code/release/comptime.zig @@ -0,0 +1,170 @@ +pub fn main() !void { + try comptimeVariable.main(); + try comptimeExpression.main(); +} + +const DuckType = struct { + // #region DuckType_max + fn max(comptime T: type, a: T, b: T) T { + return if (a > b) a else b; + } + // #endregion DuckType_max + + // #region DuckType_maxPlus + fn maxPlus(comptime T: type, a: T, b: T) T { + if (T == bool) { + return a or b; + } else if (a > b) { + return a; + } else { + return b; + } + } + // #endregion DuckType_maxPlus + + // #region DuckType_max_actual + fn max_actual(a: bool, b: bool) bool { + { + return a or b; + } + } + // #endregion DuckType_max_actual +}; + +const comptimeVariable = struct { + // #region comptimeVariable + const expect = @import("std").testing.expect; + + const CmdFn = struct { + name: []const u8, + func: fn (i32) i32, + }; + + // 这里的 cmd_fns 是一个常量,所以它是编译期可知的 + const cmd_fns = [_]CmdFn{ + CmdFn{ .name = "one", .func = one }, + CmdFn{ .name = "two", .func = two }, + CmdFn{ .name = "three", .func = three }, + }; + + fn one(value: i32) i32 { + return value + 1; + } + fn two(value: i32) i32 { + return value + 2; + } + fn three(value: i32) i32 { + return value + 3; + } + + // #region comptimeVariable_default + fn performFn(comptime prefix_char: u8, start_value: i32) i32 { + var result: i32 = start_value; + // 以下的变量 i 被标记为编译期已知的 + comptime var i = 0; + // 这里将会被内联,实际编译出来的代码将不包含循环 + // 原因是cmd_fns是一个常量,那么代表它是编译期可知的 + // 也就是说整个循环的执行结果在编译期就可以确定 + inline while (i < cmd_fns.len) : (i += 1) { + if (cmd_fns[i].name[0] == prefix_char) { + result = cmd_fns[i].func(result); + } + } + return result; + } + // #endregion comptimeVariable_default + + pub fn main() !void { + try expect(performFn('t', 1) == 6); + try expect(performFn('o', 0) == 1); + try expect(performFn('w', 99) == 99); + } + // #endregion comptimeVariable + + // #region comptimeVariable_t + fn performFn_for_t(start_value: i32) i32 { + var result: i32 = start_value; + result = two(result); + result = three(result); + return result; + } + // #endregion comptimeVariable_t + + // #region comptimeVariable_o + fn performFn_for_o(start_value: i32) i32 { + var result: i32 = start_value; + result = one(result); + return result; + } + // #endregion comptimeVariable_o + + // #region comptimeVariable_w + fn performFn_for_w(start_value: i32) i32 { + var result: i32 = start_value; + _ = &result; + return result; + } + // #endregion comptimeVariable_w +}; + +const comptimeExpression = struct { + // #region comptimeExpression + fn fibonacci(index: u32) u32 { + if (index < 2) return index; + return fibonacci(index - 1) + fibonacci(index - 2); + } + + pub fn main() !void { + const expect = @import("std").testing.expect; + + // 运行时测试 + try expect(fibonacci(7) == 13); + + // 编译期测试 + try comptime expect(fibonacci(7) == 13); + } + // #endregion comptimeExpression + + // #region comptimeExpression_container + const c = add_comptime(1, 2); + + fn add_comptime(comptime a: usize, comptime b: usize) usize { + return a + b; + } + // #endregion comptimeExpression_container +}; + +const GenericDataStruct = struct { + // #region GenericDataStruct + fn List(comptime T: type) type { + return struct { + items: []T, + len: usize, + }; + } + + var buffer: [10]i32 = undefined; + + var list = List(i32){ + .items = &buffer, + .len = 0, + }; + // #endregion GenericDataStruct + + // #region GenericDataStruct_node + const Node = struct { + next: ?*Node, + name: []const u8, + }; + + var node_a = Node{ + .next = null, + .name = "Node A", + }; + + var node_b = Node{ + .next = &node_a, + .name = "Node B", + }; + // #endregion GenericDataStruct_node +}; diff --git a/course/code/release/decision.zig b/course/code/release/decision.zig new file mode 100644 index 00000000..3b47914e --- /dev/null +++ b/course/code/release/decision.zig @@ -0,0 +1,164 @@ +pub fn main() !void { + try Basic.main(); + try MatchEnum.main(); + try TernayExpress.main(); + try DestructOptional.main(); + try DestructErrorUnion.main(); + try DestructErrorOptionalUnion.main(); +} + +const Basic = struct { + // #region more_if + const print = @import("std").debug.print; + + pub fn main() !void { + // #region default_if + const num: u8 = 1; + if (num == 1) { + print("num is 1\n", .{}); + } else if (num == 2) { + print("num is 2\n", .{}); + } else { + print("num is other\n", .{}); + } + // #endregion default_if + } + // #endregion more_if +}; + +const MatchEnum = struct { + // #region more_match_enum + const std = @import("std"); + + pub fn main() !void { + // #region default_match_enum + const Small = enum { + one, + two, + three, + four, + }; + + const demo = Small.one; + if (demo == Small.one) { + std.debug.print("{}\n", .{demo}); + } + // #endregion default_match_enum + } + // #endregion more_match_enum +}; + +const TernayExpress = struct { + // #region more_ternary + const print = @import("std").debug.print; + + pub fn main() !void { + // #region default_ternary + const a: u32 = 5; + const b: u32 = 4; + // 下方 result 的值应该是47 + const result = if (a != b) 47 else 3089; + + print("result is {}\n", .{result}); + // #endregion default_ternary + } + // #endregion more_ternary +}; + +const DestructOptional = struct { + const std = @import("std"); + const expect = std.testing.expect; + + fn a() !void { + // #region destruct_optional + const val: ?u32 = null; + if (val) |real_b| { + _ = real_b; + } else { + try expect(true); + } + // #endregion destruct_optional + } + + fn b() !void { + // #region capture_optional_pointer + var c: ?u32 = 3; + if (c) |*value| { + value.* = 2; + } + // #endregion capture_optional_pointer + } + + pub fn main() !void { + try a(); + try b(); + } +}; + +const DestructErrorUnion = struct { + const std = @import("std"); + const expect = std.testing.expect; + + fn a() !void { + // #region destruct_error_union + const val: anyerror!u32 = 0; + if (val) |value| { + try expect(value == 0); + } else |err| { + _ = err; + unreachable; + } + // #endregion destruct_error_union + } + + fn b() !void { + const val: anyerror!u32 = error.BadValue; + // #region only_catch_error + if (val) |_| {} else |err| { + try expect(err == error.BadValue); + } + // #endregion only_catch_error + } + + fn c() !void { + // #region catch_pointer + var val: anyerror!u32 = 3; + if (val) |*value| { + value.* = 9; + } else |_| { + unreachable; + } + // #endregion catch_pointer + } + + pub fn main() !void { + try a(); + try b(); + try c(); + } +}; + +const DestructErrorOptionalUnion = struct { + const std = @import("std"); + const expect = std.testing.expect; + pub fn main() !void { + // #region destruct_error_optional_union + const a: anyerror!?u32 = 0; + if (a) |optional_value| { + try expect(optional_value.? == 0); + } else |err| { + _ = err; + } + // #endregion destruct_error_optional_union + // #region destruct_error_optional_union_pointer + var d: anyerror!?u32 = 3; + if (d) |*optional_value| { + if (optional_value.*) |*value| { + value.* = 9; + } + } else |_| { + // nothing + } + // #endregion destruct_error_optional_union_pointer + } +}; diff --git a/course/code/release/defer.zig b/course/code/release/defer.zig new file mode 100644 index 00000000..7fda8827 --- /dev/null +++ b/course/code/release/defer.zig @@ -0,0 +1,19 @@ +// #region Defer +const std = @import("std"); +const print = std.debug.print; + +pub fn main() !void { + defer print("exec third\n", .{}); + + if (false) { + defer print("will not exec\n", .{}); + } + + defer { + print("exec second\n", .{}); + } + defer { + print("exec first\n", .{}); + } +} +// #endregion Defer diff --git a/course/code/release/define_variable.zig b/course/code/release/define_variable.zig new file mode 100644 index 00000000..8db019f5 --- /dev/null +++ b/course/code/release/define_variable.zig @@ -0,0 +1,211 @@ +// #region top-level +//! 顶层文档注释 +//! 顶层文档注释 + +const S = struct { + //! 顶层文档注释 +}; +// #endregion top-level + +pub fn main() !void { + _ = Timestamp{ + .seconds = 0, + .nanos = 0, + }; + DefineVar.main(); + Const.main(); + Undefined.main(); + UseUndefined.main(); + Block.main(); +} + +// #region doc-comment +/// 存储时间戳的结构体,精度为纳秒 +/// (像这里就是多行文档注释) +const Timestamp = struct { + /// 自纪元开始后的秒数 (此处也是一个文档注释). + seconds: i64, // 我们可以以此代表1970年前 (此处是普通注释) + + /// 纳秒数 (文档注释). + nanos: u32, + + /// 返回一个 Timestamp 结构体代表 unix 纪元; + /// 1970年 1月1日 00:00:00 UTC (文档注释). + pub fn unixEpoch() Timestamp { + return Timestamp{ + .seconds = 0, + .nanos = 0, + }; + } +}; +// #endregion doc-comment + +const DefineVar = struct { + // #region define + const std = @import("std"); + + pub fn main() void { + // 声明变量 variable 类型为u16, 并指定值为 666 + var variable: u16 = 0; + variable = 666; + + std.debug.print("变量 variable 是{}\n", .{variable}); + } + // #endregion define +}; + +const Const = struct { + // #region const + const std = @import("std"); + + pub fn main() void { + const constant: u16 = 666; + + std.debug.print("常量 constant 是{}\n", .{constant}); + } + // #endregion const +}; + +const Undefined = struct { + // #region undefined + const std = @import("std"); + + pub fn main() void { + var variable: u16 = undefined; + + variable = 666; + + std.debug.print("变量 variable 是{}\n", .{variable}); + } + // #endregion undefined +}; + +const UseUndefined = struct { + // #region use-undefined + const std = @import("std"); + + // 填充连续递增的数字 + // 注意该函数中并没有对 output 进行读操作,所以 output 的初始值不重要 + fn iota(init: u8, output: []u8) void { + for (output, init..) |*e, v| { + e.* = @intCast(v); + } + } + + pub fn main() void { + // buffer 定义时不需要初始化 + var buffer: [8]u8 = undefined; + + // 因为 iota() 会为 buffer 里的元素赋值 + iota(7, &buffer); + + // 输出 { 7, 8, 9, 10, 11, 12, 13, 14 } + std.debug.print("{any}\n", .{buffer}); + } + // #endregion use-undefined +}; + +// #region identifier +const @"identifier with spaces in it" = 0xff; +const @"1SmallStep4Man" = 112358; + +const c = @import("std").c; +pub extern "c" fn @"error"() void; +pub extern "c" fn @"fstat$INODE64"(fd: c.fd_t, buf: *c.Stat) c_int; + +const Color = enum { + red, + @"really red", +}; +const color: Color = .@"really red"; +// #endregion identifier + +const Block = struct { + pub fn main() void { + // #region block + var y: i32 = 123; + + const x = blk: { + y += 1; + break :blk y; + }; + // #endregion block + _ = x; + } +}; + +const Deconstruct = struct { + fn main() void { + // #region deconstruct + const print = @import("std").debug.print; + var x: u32 = undefined; + var y: u32 = undefined; + var z: u32 = undefined; + // 元组 + const tuple = .{ 1, 2, 3 }; + // 解构元组 + x, y, z = tuple; + + print("tuple: x = {}, y = {}, z = {}\n", .{ x, y, z }); + // 数组 + const array = [_]u32{ 4, 5, 6 }; + // 解构数组 + x, y, z = array; + + print("array: x = {}, y = {}, z = {}\n", .{ x, y, z }); + // 向量定义 + const vector: @Vector(3, u32) = .{ 7, 8, 9 }; + // 解构向量 + x, y, z = vector; + + print("vector: x = {}, y = {}, z = {}\n", .{ x, y, z }); + // #endregion deconstruct + + } +}; + +const Deconstruct_2 = struct { + pub fn main() !void { + // #region deconstruct_2 + const print = @import("std").debug.print; + var x: u32 = undefined; + + const tuple = .{ 1, 2, 3 }; + + x, var y: u32, const z = tuple; + + print("x = {}, y = {}, z = {}\n", .{ x, y, z }); + + // y 可变 + y = 100; + + // 可以用 _ 丢弃不想要的值 + _, x, _ = tuple; + + print("x = {}", .{x}); + // #endregion deconstruct_2 + } +}; + +const ThreadLocal = struct { + // #region threadlocal + const std = @import("std"); + threadlocal var x: i32 = 1234; + + fn main() !void { + const thread1 = try std.Thread.spawn(.{}, testTls, .{}); + const thread2 = try std.Thread.spawn(.{}, testTls, .{}); + testTls(); + thread1.join(); + thread2.join(); + } + + fn testTls() void { + // 1234 + std.debug.print("x is {}\n", .{x}); + x += 1; + // 1235 + std.debug.print("x is {}\n", .{x}); + } + // #endregion threadlocal +}; diff --git a/course/code/release/echo_tcp_server.zig b/course/code/release/echo_tcp_server.zig new file mode 100644 index 00000000..f1b64eaf --- /dev/null +++ b/course/code/release/echo_tcp_server.zig @@ -0,0 +1,80 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Io = std.Io; +const net = Io.net; + +pub fn main() !void { + // #region listen + // 初始化 Threaded I/O 后端(单线程模式) + var threaded: Io.Threaded = .init_single_threaded; + defer threaded.deinit(); + const io = threaded.io(); + + // 解析地址并监听 + const port: u16 = 8080; + const address: net.IpAddress = .{ .ip4 = .loopback(port) }; + + // 初始化一个server,这里就包含了 socket() 和 bind() 两个过程 + var server = try address.listen(io, .{ .reuse_address = true }); + defer server.deinit(io); + // #endregion listen + + std.log.info("start listening at {d}...", .{port}); + + // 无限循环,等待客户端连接 + while (true) { + // #region new-connection + // 等待新的连接 + std.log.info("waiting for client...", .{}); + const stream = try server.accept(io); + std.log.info("new client connected!", .{}); + // #endregion new-connection + + // #region exist-connections + // 处理客户端数据(简化版本:一次处理一个客户端) + // 初始化读写缓冲区 + var read_buffer: [4096]u8 = undefined; + var write_buffer: [4096]u8 = undefined; + var reader = stream.reader(io, &read_buffer); + var writer = stream.writer(io, &write_buffer); + + while (true) { + // 读取客户端发送的数据 + // 使用 peekGreedy(1) 获取至少 1 字节,返回所有可用数据 + const data = reader.interface.peekGreedy(1) catch |err| { + if (err == error.EndOfStream) { + std.log.info("client disconnected", .{}); + break; + } + if (reader.err) |read_err| { + std.log.err("read error: {}", .{read_err}); + } + break; + }; + + if (data.len == 0) { + std.log.info("client disconnected", .{}); + break; + } + + // 消费已读取的数据 + reader.interface.toss(data.len); + + // 将数据写回给客户端(echo) + writer.interface.writeAll(data) catch |err| { + std.log.err("write error: {}", .{err}); + if (writer.err) |write_err| { + std.log.err("underlying error: {}", .{write_err}); + } + break; + }; + writer.interface.flush() catch |err| { + std.log.err("flush error: {}", .{err}); + break; + }; + } + + stream.close(io); + // #endregion exist-connections + } +} diff --git a/course/code/release/enum.zig b/course/code/release/enum.zig new file mode 100644 index 00000000..ab471b21 --- /dev/null +++ b/course/code/release/enum.zig @@ -0,0 +1,161 @@ +pub fn main() !void { + try EnumSize.main(); + try EnumReference.main(); +} + +// #region basic_enum +const Type = enum { + ok, + not_ok, +}; + +const c = Type.ok; +// #endregion basic_enum + +// #region enum_with_value +// 指定枚举的标记类型 +// 现在我们可以在 u2 和 Value 这个枚举类型之中任意切换了 +const Value = enum(u2) { + zero, + one, + two, +}; +// #endregion enum_with_value + +// #region enum_with_value2 +const Value2 = enum(u32) { + hundred = 100, + thousand = 1000, + million = 1000000, +}; + +// 覆盖部分值 +const Value3 = enum(u4) { + a, + b = 8, + c, + d = 4, + e, +}; +// #endregion enum_with_value2 + +// #region enum_with_method +const Suit = enum { + clubs, + spades, + diamonds, + hearts, + + pub fn isClubs(self: Suit) bool { + return self == Suit.clubs; + } +}; +// #endregion enum_with_method + +const EnumSize = struct { + + // #region enum_size + const std = @import("std"); + const expect = std.testing.expect; + const mem = std.mem; + + const Small = enum { + one, + two, + three, + four, + }; + + pub fn main() !void { + try expect(@typeInfo(Small).@"enum".tag_type == u2); + try expect(@typeInfo(Small).@"enum".fields.len == 4); + try expect(mem.eql(u8, @typeInfo(Small).@"enum".fields[1].name, "two")); + try expect(mem.eql(u8, @tagName(Small.three), "three")); + } + // #endregion enum_size +}; + +const EnumReference = struct { + // #region enum_reference + const Color = enum { + auto, + off, + on, + }; + + pub fn main() !void { + const color1: Color = .auto; // 此处枚举进行了自动推断 + const color2 = Color.auto; + _ = (color1 == color2); // 这里比较的结果是 true + } + // #endregion enum_reference +}; + +const Non_exhaustiveEnum = struct { + // #region non_exhaustive_enum + const Number = enum(u8) { + one, + two, + three, + _, + }; + + const number = Number.one; + const result = switch (number) { + .one => true, + .two, .three => false, + _ => false, + }; + // result 是 true + + const is_one = switch (number) { + .one => true, + else => false, + }; + // is_one 也是true + // #endregion non_exhaustive_enum + + const std = @import("std"); + const expect = std.testing.expect; + + pub fn main() !void { + // #region enum_from_int + const Color = enum(u4) { + red, + green, + blue, + _, + }; + + // 明确列出的枚举值 + const blue: Color = @enumFromInt(2); + try expect(blue == .blue); + + // 未列出的枚举值:8 在 u4 的范围内(0~15) + const yellow: Color = @enumFromInt(8); + try expect(@TypeOf(yellow) == Color); + try expect(@intFromEnum(yellow) == 8); + + // 42 超出了 u4 的范围,会触发未定义行为 + // const ub: Color = @enumFromInt(42); + + // #endregion enum_from_int + } +}; + +const EnumLiteral_ = struct { + const std = @import("std"); + pub fn main() !void { + // #region enum_literal + // 使用内建函数 @EnumLiteral 构造出一个 EnumLiteral 类型 + // Zig 0.16 使用 @EnumLiteral() 替代 @Type(.enum_literal) + const EnumLiteralType: type = @EnumLiteral(); + + // 定义一个常量 enum_literal,它的类型为 EnumLiteral,并赋值为 ".kkk" + const enum_literal: EnumLiteralType = .kkk; + + // 使用内建函数 @tagName 获取 enum_literal 的 tag name,并进行打印 + std.debug.print("enum_literal is {s}", .{@tagName(enum_literal)}); + // #endregion enum_literal + } +}; diff --git a/course/code/release/error_handle.zig b/course/code/release/error_handle.zig new file mode 100644 index 00000000..c65439e9 --- /dev/null +++ b/course/code/release/error_handle.zig @@ -0,0 +1,263 @@ +pub fn main() !void { + BasicUse.main(); + JustOneError.main(); +} + +const BasicUse = struct { + // #region BasicUse + const std = @import("std"); + + // 定义一个错误集合类型 + const FileOpenError = error{ + AccessDenied, + OutOfMemory, + FileNotFound, + }; + + // 定义另一个错误集合类型 + const AllocationError = error{ + OutOfMemory, + }; + + pub fn main() void { + const err = foo(AllocationError.OutOfMemory); + if (err == FileOpenError.OutOfMemory) { + std.debug.print("error is OutOfMemory\n", .{}); + } + } + + fn foo(err: AllocationError) FileOpenError { + return err; + } + // #endregion BasicUse +}; + +const JustOneError = struct { + pub fn main() void { + { + // #region JustOneError1 + const err = error.FileNotFound; + // #endregion JustOneError1 + if (err != anyerror.OutOfMemory) {} + } + + { + // #region JustOneError2 + const err = (error{FileNotFound}).FileNotFound; + // #endregion JustOneError2 + if (err != anyerror.OutOfMemory) {} + } + } +}; + +const ConvertEnglishToInteger = struct { + // #region ConvertEnglishToInteger + const std = @import("std"); + const maxInt = std.math.maxInt; + + pub fn parseU64(buf: []const u8, radix: u8) !u64 { + var x: u64 = 0; + + for (buf) |c| { + const digit = charToDigit(c); + + if (digit >= radix) { + return error.InvalidChar; + } + + // x *= radix + var ov = @mulWithOverflow(x, radix); + if (ov[1] != 0) return error.OverFlow; + + // x += digit + ov = @addWithOverflow(ov[0], digit); + if (ov[1] != 0) return error.OverFlow; + x = ov[0]; + } + + return x; + } + + fn charToDigit(c: u8) u8 { + return switch (c) { + '0'...'9' => c - '0', + 'A'...'Z' => c - 'A' + 10, + 'a'...'z' => c - 'a' + 10, + else => maxInt(u8), + }; + } + // #endregion ConvertEnglishToInteger +}; + +test "parse u64" { + const result = try ConvertEnglishToInteger.parseU64("1234", 10); + try @import("std").testing.expect(result == 1234); +} + +const CatchBasic = struct { + const parseU64 = ConvertEnglishToInteger.parseU64; + // #region CatchBasic + fn doAThing(str: []u8) void { + const number = parseU64(str, 10) catch 13; + _ = number; // ... + } + // #endregion CatchBasic +}; + +const CatchAdvanced = struct { + const parseU64 = ConvertEnglishToInteger.parseU64; + // #region CatchAdvanced + fn doAThing(str: []u8) void { + const number = parseU64(str, 10) catch blk: { + // 指定某些复杂逻辑处理 + break :blk 13; + }; + _ = number; // 这里的 number 已经被初始化 + } + // #endregion CatchAdvanced +}; + +const TryBasic = struct { + const parseU64 = ConvertEnglishToInteger.parseU64; + // #region TryBasic1 + fn doAThing1(str: []u8) !void { + const number = try parseU64(str, 10); + _ = number; + } + // #endregion TryBasic1 + + // #region TryBasic2 + fn doAThing2(str: []u8) !void { + const number = parseU64(str, 10) catch |err| return err; + _ = number; + } + // #endregion TryBasic2 +}; + +const AssertNoError = struct { + const parseU64 = ConvertEnglishToInteger.parseU64; + // #region AssertNoError + const number = parseU64("1234", 10) catch unreachable; + // #endregion AssertNoError +}; + +const PreciseErrorHandle = struct { + const parseU64 = ConvertEnglishToInteger.parseU64; + fn doSomethingWithNumber(_: u64) void {} + + // #region PreciseErrorHandle + fn doAThing(str: []u8) void { + if (parseU64(str, 10)) |number| { + doSomethingWithNumber(number); + } else |err| switch (err) { + error.Overflow => { + // 处理溢出 + }, + // 此处假定这个错误不会发生 + error.InvalidChar => unreachable, + // 这里你也可以使用 else 来捕获额外的错误 + else => |leftover_err| return leftover_err, + } + } + // #endregion PreciseErrorHandle +}; + +const NotHandleError = struct { + const parseU64 = ConvertEnglishToInteger.parseU64; + fn doSomethingWithNumber(_: u64) void {} + + // #region NotHandleError + fn doADifferentThing(str: []u8) void { + if (parseU64(str, 10)) |number| { + doSomethingWithNumber(number); + } else |_| { + // 你也可以在这里做点额外的事情 + } + // 或者你也可以这样: + parseU64(str, 10) catch {}; + } + // #endregion NotHandleError +}; + +const ErrDefer = struct { + const std = @import("std"); + + // #region DeferErrorCapture + fn deferErrorCaptureExample() !void { + // 捕获错误 + errdefer |err| { + std.debug.print("the error is {s}\n", .{@errorName(err)}); + } + + return error.DeferError; + } + // #endregion DeferErrorCapture +}; + +const DeferErrDefer = struct { + // #region DeferErrDefer + const std = @import("std"); + const Allocator = std.mem.Allocator; + + const Foo = struct { + data: u32, + }; + + fn tryToAllocateFoo(allocator: Allocator) !*Foo { + return allocator.create(Foo); + } + + fn deallocateFoo(allocator: Allocator, foo: *Foo) void { + allocator.destroy(foo); + } + + fn getFooData() !u32 { + return 666; + } + + fn createFoo(allocator: Allocator, param: i32) !*Foo { + const foo = getFoo: { + var foo = try tryToAllocateFoo(allocator); + errdefer deallocateFoo(allocator, foo); + + foo.data = try getFooData(); + + break :getFoo foo; + }; + // This lasts for the rest of the function + errdefer deallocateFoo(allocator, foo); + + // Error is now properly handled by errdefer + if (param > 1337) return error.InvalidParam; + + return foo; + } + // #endregion DeferErrDefer +}; + +test "createFoo" { + try @import("std").testing.expectError(error.InvalidParam, DeferErrDefer.createFoo(@import("std").testing.allocator, 2468)); +} + +const ReferError = struct { + + // #region ReferError + // 由编译器推导而出的错误集 + pub fn add_inferred(comptime T: type, a: T, b: T) !T { + const ov = @addWithOverflow(a, b); + if (ov[1] != 0) return error.Overflow; + return ov[0]; + } + + // 明确声明的错误集 + pub fn add_explicit(comptime T: type, a: T, b: T) Error!T { + const ov = @addWithOverflow(a, b); + if (ov[1] != 0) return error.Overflow; + return ov[0]; + } + + const Error = error{ + Overflow, + }; + // #endregion ReferError +}; diff --git a/course/code/release/function.zig b/course/code/release/function.zig new file mode 100644 index 00000000..fa5e81de --- /dev/null +++ b/course/code/release/function.zig @@ -0,0 +1,80 @@ +//! 该文件有一部分函数没有进行测试,仅定义 +//! ExitProcess 和 atan2 函数是外部函数,不会进行测试。 +pub fn main() !void { + _ = add(1, 2); + _ = max(u8, 1, 2); + { + const num: u8 = 1; + _ = addFortyTwo(num); + } + _ = sub(2, 1); + + // 需要注意这是个死循环函数,不会返回。 + abort(); + + _ = shiftLeftOne(1); +} + +// #region add +pub fn add(a: u8, b: u8) u8 { + return a + b; +} +// #endregion add + +// #region max +fn max(comptime T: type, a: T, b: T) T { + return if (a > b) a else b; +} +// #endregion max + +// #region addFortyTwo +fn addFortyTwo(x: anytype) @TypeOf(x) { + return x + 42; +} +// #endregion addFortyTwo + +// #region ExitProcess +const WINAPI = @import("std").os.windows.WINAPI; +extern "kernel32" fn ExitProcess(exit_code: c_uint) callconv(WINAPI) noreturn; +// #endregion ExitProcess + +// #region sub +export fn sub(a: i8, b: i8) i8 { + return a - b; +} +// #endregion sub + +// #region atan2 +extern "c" fn atan2(a: f64, b: f64) f64; +// #endregion atan2 + +// #region abort +fn abort() noreturn { + @branchHint(.cold); + while (true) {} +} +// #endregion abort + +// #region shiftLeftOne +// 强制该函数在所有被调用位置内联,否则失败。 +inline fn shiftLeftOne(a: u32) u32 { + return a << 1; +} +// #endregion shiftLeftOne + +// #region closure +fn bar(comptime x: i32) fn (i32) i32 { + return struct { + pub fn foo(y: i32) i32 { + var counter = 0; + for (x..y) |i| { + if ((i % 2) == 0) { + counter += i * i; + } + } + return counter; + } + }.foo; +} + +// #endregion closure diff --git a/course/code/release/hello_world.zig b/course/code/release/hello_world.zig new file mode 100644 index 00000000..35a6ec9f --- /dev/null +++ b/course/code/release/hello_world.zig @@ -0,0 +1,60 @@ +pub fn main(init: std.process.Init) !void { + try One.main(); + try Two.main(init.io); + try Three.main(init.io); +} + +const std = @import("std"); + +const One = struct { + // #region one + pub fn main() !void { + std.debug.print("Hello, World!\n", .{}); + } + // #endregion one +}; + +const Two = struct { + // #region two + pub fn main(io: std.Io) !void { + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); + const stdout = &stdout_writer.interface; + + var stderr_buffer: [1024]u8 = undefined; + var stderr_writer = std.Io.File.stderr().writer(io, &stderr_buffer); + const stderr = &stderr_writer.interface; + + try stdout.print("Hello {s}!\n", .{"out"}); + try stderr.print("Hello {s}!\n", .{"err"}); + + try stdout.flush(); + try stderr.flush(); + } // #endregion two +}; + +const Three = struct { + // #region three + pub fn main(io: std.Io) !void { + // 定义两个缓冲区 + var stdout_buffer: [1024]u8 = undefined; // [!code focus] + var stderr_buffer: [1024]u8 = undefined; // [!code focus] + + // 获取writer句柄// [!code focus] + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); + const stdout = &stdout_writer.interface; + + // 获取writer句柄// [!code focus] + var stderr_writer = std.Io.File.stderr().writer(io, &stderr_buffer); + const stderr = &stderr_writer.interface; + + // 通过句柄写入buffer// [!code focus] + try stdout.print("Hello {s}!\n", .{"out"}); // [!code focus] + try stderr.print("Hello {s}!\n", .{"err"}); // [!code focus] + + // 尝试刷新buffer// [!code focus] + try stdout.flush(); // [!code focus] + try stderr.flush(); // [!code focus] + } + // #endregion three +}; diff --git a/course/code/release/import_dependency_build/build.zig b/course/code/release/import_dependency_build/build.zig new file mode 100644 index 00000000..c646ed7f --- /dev/null +++ b/course/code/release/import_dependency_build/build.zig @@ -0,0 +1,56 @@ +const std = @import("std"); +const args = [_][]const u8{ "zig", "build" }; + +pub fn build(b: *std.Build) !void { + const io = b.graph.io; + const full_path = try std.process.currentPathAlloc(io, b.allocator); + defer b.allocator.free(full_path); + + var dir = std.Io.Dir.openDirAbsolute(io, full_path, .{ .iterate = true }) catch |err| { + std.log.err("open path failed {s}, err is {}", .{ full_path, err }); + std.process.exit(1); + }; + defer dir.close(io); + + var iterate = dir.iterate(); + + while (iterate.next(io) catch |err| { + std.log.err("iterate examples_path failed, err is {}", .{err}); + std.process.exit(1); + }) |entry| { + // get the entry name, entry can be file or directory + const name = entry.name; + if (entry.kind == .directory) { + if (eqlu8(name, ".zig-cache") or eqlu8(name, "zig-out") or eqlu8(name, "zig-cache")) + continue; + + // build cwd + const cwd = std.fs.path.join(b.allocator, &[_][]const u8{ + full_path, + name, + }) catch |err| { + std.log.err("fmt path failed, err is {}", .{err}); + std.process.exit(1); + }; + + // open entry dir + const entry_dir = std.Io.Dir.openDirAbsolute(io, cwd, .{}) catch unreachable; + defer entry_dir.close(io); + + entry_dir.access(io, "build.zig", .{}) catch { + std.log.err("not found build.zig in path {s}", .{cwd}); + std.process.exit(1); + }; + + var child = std.process.spawn(io, .{ + .argv = &args, + .cwd = .{ .path = cwd }, + }) catch unreachable; + _ = child.wait(io) catch unreachable; + } + } +} + +fn eqlu8(a: []const u8, b: []const u8) bool { + return std.mem.eql(u8, a, b); +} diff --git a/course/code/release/import_dependency_build/pkg1/build.zig b/course/code/release/import_dependency_build/pkg1/build.zig new file mode 100644 index 00000000..71478915 --- /dev/null +++ b/course/code/release/import_dependency_build/pkg1/build.zig @@ -0,0 +1,5 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + @import("pkg2").helperFunction(b); +} diff --git a/course/code/release/import_dependency_build/pkg1/build.zig.zon b/course/code/release/import_dependency_build/pkg1/build.zig.zon new file mode 100644 index 00000000..6eceed4b --- /dev/null +++ b/course/code/release/import_dependency_build/pkg1/build.zig.zon @@ -0,0 +1,15 @@ +.{ + .name = .pkg1, + .version = "0.0.0", + .fingerprint = 0x759ba61b105d16f9, + .dependencies = .{ + .pkg2 = .{ + // path 为本地包的路径 + .path = "../pkg2", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/course/code/release/import_dependency_build/pkg2/build.zig b/course/code/release/import_dependency_build/pkg2/build.zig new file mode 100644 index 00000000..020bb13d --- /dev/null +++ b/course/code/release/import_dependency_build/pkg2/build.zig @@ -0,0 +1,9 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + _ = b; +} + +pub fn helperFunction(artifact: *std.Build) void { + _ = artifact; +} diff --git a/course/code/release/import_dependency_build/pkg2/build.zig.zon b/course/code/release/import_dependency_build/pkg2/build.zig.zon new file mode 100644 index 00000000..35893f62 --- /dev/null +++ b/course/code/release/import_dependency_build/pkg2/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .pkg2, + .version = "0.0.0", + .fingerprint = 0xec92f7a1a7362798, + .dependencies = .{}, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/course/code/release/import_vcpkg/build.zig b/course/code/release/import_vcpkg/build.zig new file mode 100644 index 00000000..7475753a --- /dev/null +++ b/course/code/release/import_vcpkg/build.zig @@ -0,0 +1,30 @@ +const std = @import("std"); +pub fn build(_: *std.Build) void {} + +const Build = struct { + pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "c_lib_import_gsl_windows-x64", + .root_module = b.addModule("c_lib_import_gsl_windows-x64", .{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + // #region c_import + // 增加 include 搜索目录 + exe.root_module.addIncludePath(.{ .cwd_relative = "D:\\vcpkg\\installed\\windows-x64\\include" }); + // 增加 lib 搜索目录 + exe.root_module.addLibraryPath(.{ .cwd_relative = "D:\\vcpkg\\installed\\windows-x64\\lib" }); + // 链接标准c库 + exe.root_module.linkSystemLibrary("c", .{}); + // 链接第三方库gsl + exe.root_module.linkSystemLibrary("gsl", .{}); + // #endregion c_import + + b.installArtifact(exe); + } +}; diff --git a/course/code/release/import_vcpkg/build.zig.zon b/course/code/release/import_vcpkg/build.zig.zon new file mode 100644 index 00000000..ec0454bf --- /dev/null +++ b/course/code/release/import_vcpkg/build.zig.zon @@ -0,0 +1,73 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .import_vcpkg, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + .fingerprint = 0x75583b9623cebdcb, + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/course/code/release/import_vcpkg/src/main.zig b/course/code/release/import_vcpkg/src/main.zig new file mode 100644 index 00000000..533f922b --- /dev/null +++ b/course/code/release/import_vcpkg/src/main.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +// #region import_gsl +const gsl = @cImport({ + @cInclude("gsl/gsl_fft_complex.h"); +}); +// #endregion import_gsl + +pub fn main() !void { + const n = 8; + + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + // #region use_gsl_fft + // [实数0,虚数0,实数1,虚数1,实数2,虚数2,...] + var data: []f64 = try allocator.alloc(f64, n * 2); + // 虚数恒为0,实数为0,1,2,... + for (0..n) |i| data[i * 2] = @floatFromInt(i); + // 快速离散傅里叶变换 + _ = gsl.gsl_fft_complex_radix2_forward(data.ptr, 1, n); + // 输出结果 + try std.io.stdout.writer().print("\n{any}\n", .{data}); + // #endregion use_gsl_fft +} diff --git a/course/code/release/interact_with_c.zig b/course/code/release/interact_with_c.zig new file mode 100644 index 00000000..7ae51e88 --- /dev/null +++ b/course/code/release/interact_with_c.zig @@ -0,0 +1,47 @@ +pub fn main() !void { + cHeaderImport.main(); +} + +const cHeaderImport = struct { + // #region cHeaderImport + // 使用 build.zig 的 addTranslateC 生成名为 "c" 的模块后导入 + const c = @import("c"); + pub fn main() void { + _ = c.printf("hello\n"); + } + // #endregion cHeaderImport +}; + +const cTranslate = struct { + // #region cTranslate + // 使用 build.zig 的 addTranslateC 生成名为 "c" 的模块后导入 + const c = @import("c"); + pub fn main() void { + _ = c; + } + // #endregion cTranslate +}; + +const external = struct { + // #region external_func + // 这是对应 C printf 的声明 + pub extern "c" fn printf(format: [*:0]const u8, ...) c_int; + // #endregion external_func + + // #region external + // 使用 callconv 声明函数调用约定为 C + fn add(count: c_int, ...) callconv(.C) c_int { + // 对应 C 的宏 va_start + var ap = @cVaStart(); + // 对应 C 的宏 va_end + defer @cVaEnd(&ap); + var i: usize = 0; + var sum: c_int = 0; + while (i < count) : (i += 1) { + // 对应 C 的宏 va_arg + sum += @cVaArg(&ap, c_int); + } + return sum; + } + // #endregion external +}; diff --git a/course/code/release/loop.zig b/course/code/release/loop.zig new file mode 100644 index 00000000..2f0c520f --- /dev/null +++ b/course/code/release/loop.zig @@ -0,0 +1,309 @@ +pub fn main() !void { + ForArray.main(); + ForHandleArray.main(); + IndexFor.main(); + MultiFor.main(); + ForAsExpression.main(); + LabelFor.main(); + try InlineFor.main(); + WhileBasic.main(); + WhileContinue.main(); + LabelWhile.main(); + try InlineWhile.main(); + WhileOptional.main(); + + WhileErrorUnion.main(); +} + +const ForArray = struct { + pub fn main() void { + // #region for_array + const items = [_]i32{ 4, 5, 3, 4, 0 }; + var sum: i32 = 0; + + for (items) |value| { + if (value == 0) { + continue; + } + sum += value; + } + // #endregion for_array + + // #region for_integer + for (0..5) |i| { + _ = i; + // do something + } + // #endregion for_integer + } +}; + +const ForHandleArray = struct { + pub fn main() void { + // #region for_handle_array + var items = [_]i32{ 3, 4, 2 }; + + for (&items) |*value| { + value.* += 1; + } + // #endregion for_handle_array + } +}; + +const IndexFor = struct { + pub fn main() void { + // #region index_for + const items = [_]i32{ 4, 5, 3, 4, 0 }; + for (items, 0..) |value, i| { + _ = value; + _ = i; + // do something + } + // #endregion index_for + } +}; + +const MultiFor = struct { + pub fn main() void { + // #region multi_for + const items = [_]usize{ 1, 2, 3 }; + const items2 = [_]usize{ 4, 5, 6 }; + + for (items, items2) |i, j| { + _ = i; + _ = j; + // do something + } + // #endregion multi_for + } +}; + +const ForAsExpression = struct { + pub fn main() void { + // #region for_as_expression + const items = [_]?i32{ 3, 4, null, 5 }; + + const result = for (items) |value| { + if (value == 5) { + break value; + } + } else 0; + // #endregion for_as_expression + + _ = result; + } +}; + +const LabelFor = struct { + pub fn main() void { + { + // #region label_for_1 + var count: usize = 0; + outer: for (1..6) |_| { + for (1..6) |_| { + count += 1; + break :outer; + } + } + // #endregion label_for_1 + } + + { + // #region label_for_2 + var count: usize = 0; + outer: for (1..9) |_| { + for (1..6) |_| { + count += 1; + continue :outer; + } + } + // #endregion label_for_2 + } + } +}; + +const InlineFor = struct { + // #region inline_for_more + const std = @import("std"); + const expect = std.testing.expect; + + // #region inline_for + pub fn main() !void { + const nums = [_]i32{ 2, 4, 6 }; + var sum: usize = 0; + inline for (nums) |i| { + const T = switch (i) { + 2 => f32, + 4 => i8, + 6 => bool, + else => unreachable, + }; + sum += typeNameLength(T); + } + try expect(sum == 9); + } + + fn typeNameLength(comptime T: type) usize { + return @typeName(T).len; + } + + // #endregion inline_for + // #endregion inline_for_more +}; + +const WhileBasic = struct { + // #region while_more + const std = @import("std"); + + pub fn main() void { + // #region while_basic + var i: usize = 0; + while (i < 10) { + if (i == 5) { + continue; + } + std.debug.print("i is {}\n", .{i}); + i += 1; + } + // #endregion while_basic + // #endregion while_more + } +}; + +const WhileContinue = struct { + pub fn main() void { + { + // #region while_continue_1 + var i: usize = 0; + while (i < 10) : (i += 1) {} + // #endregion while_continue_1 + } + + { + // #region while_continue_2 + var i: usize = 1; + var j: usize = 1; + while (i * j < 2000) : ({ + i *= 2; + j *= 3; + }) {} + // #endregion while_continue_2 + } + } +}; + +// #region while_as_expression +fn rangeHasNumber(begin: usize, end: usize, number: usize) bool { + var i = begin; + return while (i < end) : (i += 1) { + if (i == number) { + break true; + } + } else false; +} +// #endregion while_as_expression + +const LabelWhile = struct { + pub fn main() void { + { + // #region label_while_continue + var i: usize = 0; + outer: while (i < 10) : (i += 1) { + while (true) { + continue :outer; + } + } + // #endregion label_while_continue + } + { + // #region label_while_break + outer: while (true) { + while (true) { + break :outer; + } + } + // #endregion label_while_break + } + } +}; + +const InlineWhile = struct { + // #region inline_while_more + const std = @import("std"); + const expect = std.testing.expect; + + // #region inline_while + pub fn main() !void { + comptime var i = 0; + var sum: usize = 0; + inline while (i < 3) : (i += 1) { + const T = switch (i) { + 0 => f32, + 1 => i8, + 2 => bool, + else => unreachable, + }; + sum += typeNameLength(T); + } + try expect(sum == 9); + } + + fn typeNameLength(comptime T: type) usize { + return @typeName(T).len; + } + // #endregion inline_while + // #endregion inline_while_more +}; + +const WhileOptional = struct { + // #region while_optional_more + const std = @import("std"); + + var numbers_left: u32 = undefined; + fn eventuallyNullSequence() ?u32 { + return if (numbers_left == 0) null else blk: { + numbers_left -= 1; + break :blk numbers_left; + }; + } + + pub fn main() void { + var sum2: u32 = 0; + numbers_left = 3; + // #region while_optional + while (eventuallyNullSequence()) |value| { + sum2 += value; + } else { + std.debug.print("meet a null\n", .{}); + } + // 还可以使用else分支,碰到第一个 null 时触发并退出循环 + // #endregion while_optional + } + // #endregion while_optional_more +}; + +const WhileErrorUnion = struct { + // #region while_error_union_more + const std = @import("std"); + var numbers_left: u32 = undefined; + + fn eventuallyErrorSequence() anyerror!u32 { + return if (numbers_left == 0) error.ReachedZero else blk: { + numbers_left -= 1; + break :blk numbers_left; + }; + } + + pub fn main() void { + var sum1: u32 = 0; + numbers_left = 3; + // #region while_error_union + while (eventuallyErrorSequence()) |value| { + sum1 += value; + } else |err| { + std.debug.print("meet a err: {}\n", .{err}); + } + // #endregion while_error_union + } + // #endregion while_error_union_more +}; diff --git a/course/code/release/memory_manager.zig b/course/code/release/memory_manager.zig new file mode 100644 index 00000000..52ae7f16 --- /dev/null +++ b/course/code/release/memory_manager.zig @@ -0,0 +1,234 @@ +pub fn main() !void { + try DebugAllocator.main(); + try SmpAllocator.main(); + try BestAllocator.main(); + try FixedBufferAllocator.main(); + try ThreadSafeFixedBufferAllocator.main(); + try ArenaAllocator.main(); + try c_allocator.main(); + try page_allocator.main(); + try StackFallbackAllocator.main(); + try MemoryPool.main(); +} + +const DebugAllocator = struct { + // #region DebugAllocator + const std = @import("std"); + + pub fn main() !void { + // 使用模型,一定要是变量,不能是常量 + var gpa = std.heap.DebugAllocator(.{}){}; + // 拿到一个allocator + const allocator = gpa.allocator(); + + // defer 用于执行debug_allocator善后工作 + defer { + // 尝试进行 deinit 操作 + const deinit_status = gpa.deinit(); + + // 检测是否发生内存泄漏 + if (deinit_status == .leak) @panic("TEST FAIL"); + } + + //申请内存 + const bytes = try allocator.alloc(u8, 100); + // 延后释放内存 + defer allocator.free(bytes); + } + // #endregion DebugAllocator +}; + +const SmpAllocator = struct { + // #region SmpAllocator + const std = @import("std"); + + pub fn main() !void { + // 无需任何初始化,拿来就可以使用 + const allocator = std.heap.smp_allocator; + + //申请内存 + const bytes = try allocator.alloc(u8, 100); + // 延后释放内存 + defer allocator.free(bytes); + } + // #endregion SmpAllocator +}; + +const FixedBufferAllocator = struct { + // #region FixedBufferAllocator + const std = @import("std"); + + pub fn main() !void { + var buffer: [1000]u8 = undefined; + // 一块内存区域,传入到fixed buffer中 + var fba = std.heap.FixedBufferAllocator.init(&buffer); + + // 获取内存allocator + const allocator = fba.allocator(); + + // 申请内存 + const memory = try allocator.alloc(u8, 100); + // 释放内存 + defer allocator.free(memory); + } + // #endregion FixedBufferAllocator +}; + +const ThreadSafeFixedBufferAllocator = struct { + // #region ThreadSafeFixedBufferAllocator + const std = @import("std"); + + pub fn main() !void { + var buffer: [1000]u8 = undefined; + // 一块内存区域,传入到fixed buffer中 + var fba = std.heap.FixedBufferAllocator.init(&buffer); + + // 获取内存allocator + const allocator = fba.allocator(); + + // Zig 0.16 移除了 ThreadSafeAllocator。 + // 如果需要在线程间共享 FixedBufferAllocator,需要自行保护临界区。 + var mutex: std.atomic.Mutex = .unlocked; + + while (!mutex.tryLock()) { + std.atomic.spinLoopHint(); + } + defer mutex.unlock(); + + // 申请内存 + const memory = try allocator.alloc(u8, 100); + // 释放内存 + defer allocator.free(memory); + } + // #endregion ThreadSafeFixedBufferAllocator +}; + +const BestAllocator = struct { + const std = @import("std"); + const builtin = @import("builtin"); + var debug_allocator: std.heap.DebugAllocator(.{}) = .{}; + + pub fn main() !void { + const allocator, const is_debug = allocator: { + if (builtin.os.tag == .wasi) break :allocator .{ std.heap.wasm_allocator, false }; + break :allocator switch (builtin.mode) { + .Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true }, + .ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false }, + }; + }; + defer if (is_debug) { + _ = debug_allocator.deinit(); + }; + //申请内存 + const bytes = try allocator.alloc(u8, 100); + // 延后释放内存 + defer allocator.free(bytes); + } +}; + +const ArenaAllocator = struct { + // #region ArenaAllocator + const std = @import("std"); + + pub fn main() !void { + // 使用模型,一定要是变量,不能是常量 + var gpa = std.heap.DebugAllocator(.{}){}; + // 拿到一个allocator + const allocator = gpa.allocator(); + + // defer 用于执行 debug allocator 善后工作 + defer { + const deinit_status = gpa.deinit(); + + if (deinit_status == .leak) @panic("TEST FAIL"); + } + + // 对通用内存分配器进行一层包裹 + var arena = std.heap.ArenaAllocator.init(allocator); + + // defer 最后释放内存 + defer arena.deinit(); + + // 获取分配器 + const arena_allocator = arena.allocator(); + + _ = try arena_allocator.alloc(u8, 1); + _ = try arena_allocator.alloc(u8, 10); + _ = try arena_allocator.alloc(u8, 100); + } + // #endregion ArenaAllocator +}; + +const c_allocator = struct { + // #region c_allocator + const std = @import("std"); + + pub fn main() !void { + // 用起来和 C 一样纯粹 + const allocator = std.heap.c_allocator; + const num = try allocator.alloc(u8, 1); + defer allocator.free(num); + } + // #endregion c_allocator +}; + +const page_allocator = struct { + // #region page_allocator + const std = @import("std"); + + pub fn main() !void { + const allocator = std.heap.page_allocator; + const memory = try allocator.alloc(u8, 100); + defer allocator.free(memory); + } + // #endregion page_allocator +}; + +const StackFallbackAllocator = struct { + // #region stack_fallback_allocator + const std = @import("std"); + + pub fn main() !void { + // 初始化一个优先使用栈区的分配器 + // 栈区大小为256个字节,如果栈区不够用,就会使用page allocator + var stack_alloc = std.heap.stackFallback( + 256 * @sizeOf(u8), + std.heap.page_allocator, + ); + // 获取分配器 + const stack_allocator = stack_alloc.get(); + // 申请内存 + const memory = try stack_allocator.alloc(u8, 100); + // 释放内存 + defer stack_allocator.free(memory); + } + // #endregion stack_fallback_allocator +}; + +const MemoryPool = struct { + // #region MemoryPool + const std = @import("std"); + + pub fn main() !void { + // 此处为了演示,直接使用page allocator + // Zig 0.16 中 MemoryPool 使用 .empty 常量初始化 + var pool: std.heap.MemoryPool(u32) = .empty; + defer pool.deinit(std.heap.page_allocator); + + // 连续申请三个对象 + const p1 = try pool.create(std.heap.page_allocator); + const p2 = try pool.create(std.heap.page_allocator); + const p3 = try pool.create(std.heap.page_allocator); + + // 回收p2 + pool.destroy(p2); + // 再申请一个新的对象 + const p4 = try pool.create(std.heap.page_allocator); + + // 注意,此时p2和p4指向同一块内存 + _ = p1; + _ = p3; + _ = p4; + } + // #endregion MemoryPool +}; diff --git a/course/code/release/number.zig b/course/code/release/number.zig new file mode 100644 index 00000000..59241a30 --- /dev/null +++ b/course/code/release/number.zig @@ -0,0 +1,58 @@ +pub fn main() void { + // #region type + // 下划线可以放在数字之间作为视觉分隔符 + const one_billion = 1_000_000_000; + const binary_mask = 0b1_1111_1111; + const permissions = 0o7_5_5; + const big_address = 0xFF80_0000_0000_0000; + // #endregion type + + _ = one_billion; + _ = binary_mask; + _ = permissions; + _ = big_address; + + { + // #region float + const std = @import("std"); + + const inf = std.math.inf(f32); + const negative_inf = -std.math.inf(f64); + const nan = std.math.nan(f128); + // #endregion float + + _ = inf; + _ = negative_inf; + _ = nan; + } + + { + const std = @import("std"); + const print = std.debug.print; + + // #region complex + const Complex = std.math.Complex(f64); + const i = Complex.init(0, 1); + + // 虚数单位的平方 + const z1 = i.mul(i); + print("i * i = ({d:.1},{d:.1})\n", .{ z1.re, z1.im }); + // i * i = (-1.0,0.0) + + // 使用常见函数 + const z2 = std.math.complex.pow(i, Complex.init(2, 0)); + print("pow(i, 2) = ({d:.1},{d:.1})\n", .{ z2.re, z2.im }); + // pow(i, 2) = (-1.0,0.0) + + // 欧拉公式 + const z3 = std.math.complex.exp(i.mul(Complex.init(std.math.pi, 0))); + print("exp(i, pi) = ({d:.1},{d:.1})\n", .{ z3.re, z3.im }); + // exp(i, pi) = (-1.0,0.0) + + // 共轭复数 + const z4 = Complex.init(1, 2).mul(Complex.init(1, -2)); + print("(1 + 2i) * (1 - 2i) = ({d:.1},{d:.1})\n", .{ z4.re, z4.im }); + // (1 + 2i) * (1 - 2i) = (5.0,0.0) + // #endregion complex + } +} diff --git a/course/code/release/opaque.zig b/course/code/release/opaque.zig new file mode 100644 index 00000000..ad2313b6 --- /dev/null +++ b/course/code/release/opaque.zig @@ -0,0 +1,11 @@ +// #region opaque +const Derp = opaque {}; +const Wat = opaque {}; + +extern fn bar(d: *Derp) void; +fn foo(w: *Wat) callconv(.C) void { + bar(w); +} +// #endregion opaque + +pub fn main() !void {} diff --git a/course/code/release/optional_type.zig b/course/code/release/optional_type.zig new file mode 100644 index 00000000..ab242483 --- /dev/null +++ b/course/code/release/optional_type.zig @@ -0,0 +1,59 @@ +pub fn main() !void { + try ComptimeAccessOptionalType.main(); +} + +// #region basic_type +// 一个普通的i32整数 +const normal_int: i32 = 1234; + +// i32的可选类型,现在它的值可以是 i32 或者 null +const optional_int: ?i32 = 5678; +// #endregion basic_type + +const Malloc = struct { + const Foo = struct {}; + + // #region malloc + // extern 用于连接标准 libc 的 malloc 函数,它是 posix 标准之一 + extern fn malloc(size: usize) ?*u8; + + fn doAThing() ?*Foo { + // 尝试调用 malloc 申请内存,如果失败则返回null + const ptr = malloc(1234) orelse return null; + _ = ptr; // ... + } + // #endregion malloc +}; + +const CheckNull = struct { + const Foo = struct {}; + // #region check_null + fn doSomethingWithFoo(foo: *Foo) void { + _ = foo; + } + + fn doAThing(optional_foo: ?*Foo) void { + // 干点什么。。。 + if (optional_foo) |foo| { + doSomethingWithFoo(foo); + } + // 干点什么。。。 + } + // #endregion check_null +}; + +const ComptimeAccessOptionalType = struct { + const expect = @import("std").testing.expect; + pub fn main() !void { + // #region comptime_access_optional_type + // 声明一个可选类型,并赋值为 null + var foo: ?i32 = null; + + // 重新赋值为子类型的值,这里是 i32 + foo = 1234; + + // 使用编译期反射来获取 foo 的类型信息 + try comptime expect(@typeInfo(@TypeOf(foo)).optional.child == i32); + // #endregion comptime_access_optional_type + } +}; diff --git a/course/code/release/package_management_exporter/Readme.md b/course/code/release/package_management_exporter/Readme.md new file mode 100644 index 00000000..44e8e91d --- /dev/null +++ b/course/code/release/package_management_exporter/Readme.md @@ -0,0 +1 @@ +该目录仅仅是作为演示用的空项目! diff --git a/course/code/release/package_management_exporter/build.zig b/course/code/release/package_management_exporter/build.zig new file mode 100644 index 00000000..ac1784a0 --- /dev/null +++ b/course/code/release/package_management_exporter/build.zig @@ -0,0 +1,14 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // #region create_module + _ = b.addModule("exporter", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + // #endregion create_module +} diff --git a/course/code/release/package_management_exporter/build.zig.zon b/course/code/release/package_management_exporter/build.zig.zon new file mode 100644 index 00000000..3f2fcacd --- /dev/null +++ b/course/code/release/package_management_exporter/build.zig.zon @@ -0,0 +1,15 @@ +// #region package_management +.{ + // 包名字 + .name = .exporter, + // 包版本 + .version = "0.0.0", + .fingerprint = 0x6a125ce7eaa53f2, + // 包所包含的源文件,一般用于在对外提供包时才使用,还是建议养成写清楚paths的习惯 + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} +// #endregion package_management diff --git a/course/code/release/package_management_exporter/src/root.zig b/course/code/release/package_management_exporter/src/root.zig new file mode 100644 index 00000000..19ab525b --- /dev/null +++ b/course/code/release/package_management_exporter/src/root.zig @@ -0,0 +1,3 @@ +pub fn add(a: i32, b: i32) i32 { + return a + b; +} diff --git a/course/code/release/package_management_importer/Readme.md b/course/code/release/package_management_importer/Readme.md new file mode 100644 index 00000000..44e8e91d --- /dev/null +++ b/course/code/release/package_management_importer/Readme.md @@ -0,0 +1 @@ +该目录仅仅是作为演示用的空项目! diff --git a/course/code/release/package_management_importer/build.zig b/course/code/release/package_management_importer/build.zig new file mode 100644 index 00000000..62723df4 --- /dev/null +++ b/course/code/release/package_management_importer/build.zig @@ -0,0 +1,32 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "importer", + .root_module = b.addModule("importer", .{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + // #region import_module + // 通过 dependency 函数获取到依赖 + const pe = b.dependency("path-exporter", .{ + .target = target, + .optimize = optimize, + }); + const te = b.dependency("tarball-exporter", .{ + .target = target, + .optimize = optimize, + }); + // 将 module 添加到 exe 的 root module 中 + exe.root_module.addImport("path_exporter", pe.module("exporter")); + exe.root_module.addImport("tarball_exporter", te.module("msgpack")); + // #endregion import_module + + b.installArtifact(exe); +} diff --git a/course/code/release/package_management_importer/build.zig.zon b/course/code/release/package_management_importer/build.zig.zon new file mode 100644 index 00000000..f74a0521 --- /dev/null +++ b/course/code/release/package_management_importer/build.zig.zon @@ -0,0 +1,27 @@ +// #region package_management +.{ + // 包名字 + .name = .importer, + // 包版本 + .version = "0.0.0", + .fingerprint = 0x64e883e88dde22e2, + // 包依赖 + .dependencies = .{ + // 包依赖项的名字 + .@"tarball-exporter" = .{ + .url = "https://github.com/zigcc/zig-msgpack/archive/33771261cc6bba98cee380392f6e95fbca30d956.tar.gz", + .hash = "zig_msgpack-0.0.8-evvueB_ZAQBNRm7kdh1FslBxMvpu5WKvU2RrYhUY_Dne", + }, + .@"path-exporter" = .{ + // path 为本地包的路径 + .path = "../package_management_exporter", + }, + }, + // 包所包含的源文件,一般用于在对外提供包时才使用,还是建议养成写清楚paths的习惯 + .paths = .{ + "src", + "build.zig", + "build.zig.zon", + }, +} +// #endregion package_management diff --git a/course/code/release/package_management_importer/src/main.zig b/course/code/release/package_management_importer/src/main.zig new file mode 100644 index 00000000..5c0df21e --- /dev/null +++ b/course/code/release/package_management_importer/src/main.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const pe = @import("path_exporter"); +const te = @import("tarball_exporter"); + +pub fn main(init: std.process.Init) !void { + const io = init.io; + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); + const stdout = &stdout_writer.interface; + + const str2: te.Str = .{ .str = "2" }; + + try stdout.print( + \\Result of 1 + 1 + \\Path-Exporter: {} + \\Tarball-Expoter: {s} + , .{ + pe.add(1, 1), + str2.value(), + }); + try stdout.flush(); +} diff --git a/course/code/release/pointer.zig b/course/code/release/pointer.zig new file mode 100644 index 00000000..6af33843 --- /dev/null +++ b/course/code/release/pointer.zig @@ -0,0 +1,266 @@ +pub fn main() !void { + try SinglePointer.main(); + try MultiPointer.main(); + try ArrayAndSlice.main(); + try Slice.main(); + try STPointer.main(); + try Volatile.main(); + try Align.main(); + try AlignCast.main(); + try ZeroPointer.main(); + ComptimePointer.main(); + ptr2int.main(); + try compPointer.main(); +} + +const SinglePointer = struct { + // #region single_pointer + const print = @import("std").debug.print; + + pub fn main() !void { + var integer: i16 = 666; + const ptr = &integer; + ptr.* = ptr.* + 1; + + print("{}\n", .{integer}); + } + // #endregion single_pointer +}; + +const fnPointer = struct { + // #region fn_pointer + const Call2Op = *const fn (a: i8, b: i8) i8; + // Call20p 是一个函数指针类型,指向一个接受两个 i8 类型参数并返回 i8 类型的函数 + // #endregion fn_pointer +}; + +const ptr2int = struct { + pub fn main() void { + // #region ptr2int + const std = @import("std"); + + // ptrFromInt 将整数转换为指针 + const ptr: *i32 = @ptrFromInt(0xdeadbee0); + // intFromPtr 将指针转换为整数 + const addr = @intFromPtr(ptr); + + if (@TypeOf(addr) == usize) { + std.debug.print("success\n", .{}); + } + if (addr == 0xdeadbee0) { + std.debug.print("success\n", .{}); + } + // #endregion ptr2int + } +}; + +const MultiPointer = struct { + // #region multi_pointer + const print = @import("std").debug.print; + + pub fn main() !void { + const array = [_]i32{ 1, 2, 3, 4 }; + const ptr: [*]const i32 = &array; + + print("第一个元素:{}\n", .{ptr[0]}); + } + // #endregion multi_pointer +}; +const ArrayAndSlice = struct { + // #region array_and_slice + const expect = @import("std").testing.expect; + + pub fn main() !void { + var array: [5]u8 = "hello".*; + + const array_pointer = &array; + try expect(array_pointer.len == 5); + + const slice: []u8 = array[1..3]; + try expect(slice.len == 2); + } + // #endregion array_and_slice +}; + +const Slice = struct { + // #region slice + const print = @import("std").debug.print; + + pub fn main() !void { + var array = [_]i32{ 1, 2, 3, 4 }; + const arr_ptr: *const [4]i32 = &array; + + print("数组第一个元素为:{}\n", .{arr_ptr[0]}); + print("数组长度为:{}\n", .{arr_ptr.len}); + + const slice = array[1 .. array.len - 1]; + const slice_ptr: []i32 = slice; + + print("切片第一个元素为:{}\n", .{slice_ptr[0]}); + print("切片长度为:{}\n", .{slice_ptr.len}); + } + // #endregion slice +}; + +const STPointer = struct { + // #region st_pointer + const std = @import("std"); + + // 我们也可以用 std.c.printf 代替 + pub extern "c" fn printf(format: [*:0]const u8, ...) c_int; + + pub fn main() anyerror!void { + _ = printf("Hello, world!\n"); // OK + } + // #endregion st_pointer +}; + +const Volatile = struct { + // #region volatile + // expect 是单元测试的断言函数 + const expect = @import("std").testing.expect; + + pub fn main() !void { + const mmio_ptr: *volatile u8 = @ptrFromInt(0x12345678); + try expect(@TypeOf(mmio_ptr) == *volatile u8); + } + // #endregion volatile +}; + +const Align = struct { + // #region align + const std = @import("std"); + const builtin = @import("builtin"); + const expect = std.testing.expect; + + pub fn main() !void { + var x: i32 = 1234; + // 获取内存对齐信息 + const align_of_i32 = @alignOf(@TypeOf(x)); + // 尝试比较类型 + try expect(@TypeOf(&x) == *i32); + // 尝试在设置内存对齐后再进行类型比较 + try expect(*i32 == *align(align_of_i32) i32); + + if (builtin.target.cpu.arch == .x86_64) { + // 获取了 x86_64 架构的指针对齐大小 + try expect(@typeInfo(*i32).pointer.alignment == 4); + } + } + // #endregion align +}; + +const AlignCast = struct { + // #region align_cast + const expect = @import("std").testing.expect; + + // 全局变量 + var foo: u8 align(4) = 100; + + fn derp() align(@sizeOf(usize) * 2) i32 { + return 1234; + } + + // 以下是两个函数 + fn noop1() align(1) void {} + fn noop4() align(4) void {} + + pub fn main() !void { + // 全局变量对齐 + try expect(@typeInfo(@TypeOf(&foo)).pointer.alignment == 4); + try expect(@TypeOf(&foo) == *align(4) u8); + const as_pointer_to_array: *align(4) [1]u8 = &foo; + const as_slice: []align(4) u8 = as_pointer_to_array; + const as_unaligned_slice: []u8 = as_slice; + try expect(as_unaligned_slice[0] == 100); + + // 函数对齐 + try expect(derp() == 1234); + try expect(@TypeOf(derp) == fn () i32); + try expect(@TypeOf(&derp) == *align(@sizeOf(usize) * 2) const fn () i32); + + noop1(); + try expect(@TypeOf(noop1) == fn () void); + try expect(@TypeOf(&noop1) == *align(1) const fn () void); + + noop4(); + try expect(@TypeOf(noop4) == fn () void); + try expect(@TypeOf(&noop4) == *align(4) const fn () void); + } + // #endregion align_cast +}; + +const ZeroPointer = struct { + // #region zero_pointer + // 本示例中仅仅是构建了一个零指针 + // 并未使用,故可以在所有平台运行 + const std = @import("std"); + const expect = std.testing.expect; + + pub fn main() !void { + const zero: usize = 0; + const ptr: *allowzero i32 = @ptrFromInt(zero); + try expect(@intFromPtr(ptr) == 0); + } + // #endregion zero_pointer +}; + +const ComptimePointer = struct { + // #region comptime_pointer + const expect = @import("std").testing.expect; + + pub fn main() void { + comptime { + // 在这个 comptime 块中,可以正常使用pointer + // 不依赖于编译结果的内存布局,即在编译期时不依赖于未定义的内存布局 + var x: i32 = 1; + const ptr = &x; + ptr.* += 1; + x += 1; + try expect(ptr.* == 3); + } + } + // #endregion comptime_pointer +}; + +const compPointer = struct { + pub fn main() !void { + // #region comp_pointer + comptime { + const expect = @import("std").testing.expect; + // 只要指针不被解引用,那么就可以这么做 + const ptr: *i32 = @ptrFromInt(0xdeadbee0); + const addr = @intFromPtr(ptr); + try expect(@TypeOf(addr) == usize); + try expect(addr == 0xdeadbee0); + } + // #endregion comp_pointer + } +}; + +const ptrCast = struct { + const std = @import("std"); + pub fn main() !void { + // #region ptr_cast + const bytes align(@alignOf(u32)) = [_]u8{ 0x12, 0x12, 0x12, 0x12 }; + // 将 u8数组指针 转换为 u32 类型的指针 + const u32_ptr: *const u32 = @ptrCast(&bytes); + + if (u32_ptr.* == 0x12121212) { + std.debug.print("success\n", .{}); + } + + // 通过标准库转为 u32 + const u32_value = std.mem.bytesAsSlice(u32, bytes[0..])[0]; + + if (u32_value == 0x12121212) { + std.debug.print("success\n", .{}); + } + + // 通过内置函数转换 + if (@as(u32, @bitCast(bytes)) == 0x12121212) { + std.debug.print("success\n", .{}); + } + // #endregion ptr_cast + } +}; diff --git a/course/code/release/reflection.zig b/course/code/release/reflection.zig new file mode 100644 index 00000000..7ededd0e --- /dev/null +++ b/course/code/release/reflection.zig @@ -0,0 +1,270 @@ +pub fn main() !void { + typeName.main(); + typeInfo.main(); + hasDecl.main(); + hasField.main(); + + Field.main(); + fieldParentPtr.main(); + call.main(); + Type.main(); +} + +test "all" { + _ = NoEffects; + _ = TypeInfo2; + _ = TypeInfo3; +} + +const NoEffects = struct { + // #region no_effects + const std = @import("std"); + const expect = std.testing.expect; + + test "no runtime side effects" { + var data: i32 = 0; + const T = @TypeOf(foo(i32, &data)); + try comptime expect(T == i32); + try expect(data == 0); + } + + fn foo(comptime T: type, ptr: *T) T { + ptr.* += 1; + return ptr.*; + } + // #endregion no_effects +}; + +const typeName = struct { + // #region typeName + const std = @import("std"); + + const T = struct { + const Y = struct {}; + }; + + pub fn main() void { + std.debug.print("{s}\n", .{@typeName(T)}); + std.debug.print("{s}\n", .{@typeName(T.Y)}); + } + // #endregion typeName +}; + +const typeInfo = struct { + // #region typeInfo + const std = @import("std"); + + const T = struct { + a: u8, + b: u8, + }; + + pub fn main() void { + // 通过 @typeInfo 获取类型信息 + const type_info = @typeInfo(T); + // 断言它为 struct + const struct_info = type_info.@"struct"; + + // inline for 打印该结构体内部字段的信息 + inline for (struct_info.fields) |field| { + std.debug.print("field name is {s}, field type is {}\n", .{ + field.name, + field.type, + }); + } + } + // #endregion typeInfo +}; + +const TypeInfo2 = struct { + // #region TypeInfo2 + const std = @import("std"); + + fn IntToArray(comptime T: type) type { + // 获得类型信息,并断言为Int + const int_info = @typeInfo(T).int; + // 获得Int位数 + const bits = int_info.bits; + // 检查位数是否被8整除 + if (bits % 8 != 0) @compileError("bit count not a multiple of 8"); + // 生成新类型 + return [bits / 8]u8; + } + + test { + try std.testing.expectEqual([1]u8, IntToArray(u8)); + try std.testing.expectEqual([2]u8, IntToArray(u16)); + try std.testing.expectEqual([3]u8, IntToArray(u24)); + try std.testing.expectEqual([4]u8, IntToArray(u32)); + } + // #endregion TypeInfo2 +}; + +const TypeInfo3 = struct { + // #region TypeInfo3 + const std = @import("std"); + + fn ExternAlignOne(comptime T: type) type { + // 获得类型信息,并断言为Struct. + const struct_info = @typeInfo(T).@"struct"; + // 准备字段名称 + comptime var field_names: [struct_info.fields.len][]const u8 = undefined; + comptime var field_types: [struct_info.fields.len]type = undefined; + comptime var field_attrs: [struct_info.fields.len]std.builtin.Type.StructField.Attributes = undefined; + + inline for (struct_info.fields, 0..) |field, i| { + field_names[i] = field.name; + field_types[i] = field.type; + // 设置对齐为 1,其他属性使用默认值 + field_attrs[i] = .{ + .@"align" = 1, + }; + } + + // 使用 @Struct 构造新类型(extern 布局,对齐为 1) + return @Struct(.@"extern", null, &field_names, &field_types, &field_attrs); + } + + const MyStruct = struct { + a: u32, + b: u32, + }; + + test { + const NewType = ExternAlignOne(MyStruct); + try std.testing.expectEqual(4, @alignOf(MyStruct)); + try std.testing.expectEqual(1, @alignOf(NewType)); + } + // #endregion TypeInfo3 +}; + +const hasDecl = struct { + // #region hasDecl + const std = @import("std"); + + const Foo = struct { + nope: i32, + + pub var blah = "xxx"; + const hi = 1; + }; + + pub fn main() void { + // true + std.debug.print("blah:{}\n", .{@hasDecl(Foo, "blah")}); + // true + // hi 此声明可以被检测到是因为类型和代码处于同一个文件中,这导致他们之间可以互相访问 + // 换另一个文件就不行了 + std.debug.print("hi:{}\n", .{@hasDecl(Foo, "hi")}); + // false 不检查字段 + std.debug.print("nope:{}\n", .{@hasDecl(Foo, "nope")}); + // false 没有对应的声明 + std.debug.print("nope1234:{}\n", .{@hasDecl(Foo, "nope1234")}); + } + // #endregion hasDecl +}; + +const hasField = struct { + // #region hasField + const std = @import("std"); + + const Foo = struct { + nope: i32, + + pub var blah = "xxx"; + const hi = 1; + }; + + pub fn main() void { + // false + std.debug.print("blah:{}\n", .{@hasField(Foo, "blah")}); + // false + std.debug.print("hi:{}\n", .{@hasField(Foo, "hi")}); + // true + std.debug.print("nope:{}\n", .{@hasField(Foo, "nope")}); + // false + std.debug.print("nope1234:{}\n", .{@hasField(Foo, "nope1234")}); + } + // #endregion hasField +}; + +const Field = struct { + // #region Field + const std = @import("std"); + + const Point = struct { + x: u32, + y: u32, + + pub var z: u32 = 1; + }; + + pub fn main() void { + var p = Point{ .x = 0, .y = 0 }; + + @field(p, "x") = 4; + @field(p, "y") = @field(p, "x") + 1; + // x is 4, y is 5 + std.debug.print("x is {}, y is {}\n", .{ p.x, p.y }); + + // Point's z is 1 + std.debug.print("Point's z is {}\n", .{@field(Point, "z")}); + } + // #endregion Field +}; + +const fieldParentPtr = struct { + // #region fieldParentPtr + const std = @import("std"); + + const Point = struct { + x: u32, + }; + + pub fn main() void { + var p = Point{ .x = 0 }; + + const res = &p == @as(*Point, @fieldParentPtr("x", &p.x)); + + // test is true + std.debug.print("test is {}\n", .{res}); + } + // #endregion fieldParentPtr +}; + +const call = struct { + // #region call + const std = @import("std"); + + fn add(a: i32, b: i32) i32 { + return a + b; + } + + pub fn main() void { + std.debug.print("call function add, the result is {}\n", .{@call(.auto, add, .{ 1, 2 })}); + } + // #endregion call +}; + +const Type = struct { + // #region Type + const std = @import("std"); + + // Zig 0.16 使用 @Struct 替代 @Type + const T = @Struct( + .auto, // layout + null, // BackingInt + &.{"b"}, // field_names + &.{u32}, // field_types + &.{.{ .@"align" = 8 }}, // field_attrs + ); + + pub fn main() void { + const D = T{ + .b = 666, + }; + + std.debug.print("{}\n", .{D.b}); + } + // #endregion Type +}; diff --git a/course/code/release/result-location.zig b/course/code/release/result-location.zig new file mode 100644 index 00000000..638cce6f --- /dev/null +++ b/course/code/release/result-location.zig @@ -0,0 +1,390 @@ +const std = @import("std"); + +pub fn main() !void { + BasicInference.main(); + ResultTypeVariable.main(); + ResultTypeReturn.main(); + ResultTypeParam.main(); + ResultTypeFieldDefault.main(); + ResultLocationNested.main(); + DeclLiteralBasic.main(); + DeclLiteralFieldDefault.main(); + DeclLiteralFunction.main(); + // 以下示例需要 allocator,仅在测试中运行 + // try DeclLiteralErrorUnion.main(); + // try StdLibArrayList.main(); + try StdLibDebugAllocator.main(); +} + +const BasicInference = struct { + // #region basic_inference + const Point = struct { + x: i32, + y: i32, + }; + + pub fn main() void { + // 编译器从变量类型推断出 .{} 的具体类型 + const pt: Point = .{ .x = 10, .y = 20 }; + + // 等价于 + const pt2: Point = Point{ .x = 10, .y = 20 }; + + std.debug.print("pt: ({}, {}), pt2: ({}, {})\n", .{ pt.x, pt.y, pt2.x, pt2.y }); + } + // #endregion basic_inference +}; + +const ResultTypeVariable = struct { + // #region result_type_variable + const Color = struct { + r: u8, + g: u8, + b: u8, + }; + + pub fn main() void { + // 结果类型是 Color + const red: Color = .{ .r = 255, .g = 0, .b = 0 }; + std.debug.print("red: ({}, {}, {})\n", .{ red.r, red.g, red.b }); + } + // #endregion result_type_variable +}; + +const ResultTypeReturn = struct { + // #region result_type_return + const Vec2 = struct { + x: f32, + y: f32, + }; + + fn origin() Vec2 { + // 结果类型是 Vec2 + return .{ .x = 0, .y = 0 }; + } + + pub fn main() void { + const o = origin(); + std.debug.print("origin: ({d}, {d})\n", .{ o.x, o.y }); + } + // #endregion result_type_return +}; + +const ResultTypeParam = struct { + // #region result_type_param + const Size = struct { + width: u32, + height: u32, + }; + + fn calculateArea(size: Size) u64 { + return @as(u64, size.width) * size.height; + } + + pub fn main() void { + // 调用时,.{} 的结果类型是 Size + const area = calculateArea(.{ .width = 100, .height = 50 }); + std.debug.print("area: {}\n", .{area}); + } + // #endregion result_type_param +}; + +const ResultTypeFieldDefault = struct { + // #region result_type_field_default + const Config = struct { + timeout: u32 = 30, + retries: u8 = 3, + }; + + const Wrapper = struct { + // 字段类型是 Config,所以 .{} 的结果类型是 Config + config: Config = .{}, + }; + + pub fn main() void { + const w: Wrapper = .{}; + std.debug.print("timeout: {}, retries: {}\n", .{ w.config.timeout, w.config.retries }); + } + // #endregion result_type_field_default +}; + +const ResultLocationNested = struct { + // #region result_location_nested + const Inner = struct { + value: i32, + }; + + const Outer = struct { + inner: Inner, + name: []const u8, + }; + + pub fn main() void { + // 结果位置 Outer 传播到 inner 字段,使其结果类型为 Inner + const obj: Outer = .{ + .inner = .{ .value = 42 }, // 这里 .{} 的结果类型是 Inner + .name = "example", + }; + std.debug.print("inner.value: {}, name: {s}\n", .{ obj.inner.value, obj.name }); + } + // #endregion result_location_nested +}; + +const DeclLiteralBasic = struct { + // #region decl_literal_basic + const S = struct { + x: u32, + + // 类型内的常量声明 + const default: S = .{ .x = 123 }; + }; + + pub fn main() void { + // .default 会被解析为 S.default + const val: S = .default; + std.debug.print("val.x: {}\n", .{val.x}); + } + + test "decl literal" { + const val: S = .default; + try std.testing.expectEqual(123, val.x); + } + // #endregion decl_literal_basic +}; + +const DeclLiteralFieldDefault = struct { + // #region decl_literal_field_default + const Settings = struct { + x: u32, + y: u32, + + const default: Settings = .{ .x = 1, .y = 2 }; + const high_performance: Settings = .{ .x = 100, .y = 200 }; + }; + + const Application = struct { + // 使用声明字面量设置默认值 + settings: Settings = .default, + }; + + pub fn main() void { + const app1: Application = .{}; + std.debug.print("app1.settings: ({}, {})\n", .{ app1.settings.x, app1.settings.y }); + + // 也可以覆盖为其他预定义值 + const app2: Application = .{ .settings = .high_performance }; + std.debug.print("app2.settings: ({}, {})\n", .{ app2.settings.x, app2.settings.y }); + } + + test "decl literal in field default" { + const app1: Application = .{}; + try std.testing.expectEqual(1, app1.settings.x); + + const app2: Application = .{ .settings = .high_performance }; + try std.testing.expectEqual(100, app2.settings.x); + } + // #endregion decl_literal_field_default +}; + +const DeclLiteralFunction = struct { + // #region decl_literal_function + const Point = struct { + x: i32, + y: i32, + + fn init(val: i32) Point { + return .{ .x = val, .y = val }; + } + + fn offset(val: i32, dx: i32, dy: i32) Point { + return .{ .x = val + dx, .y = val + dy }; + } + }; + + pub fn main() void { + // .init(5) 等价于 Point.init(5) + const p1: Point = .init(5); + std.debug.print("p1: ({}, {})\n", .{ p1.x, p1.y }); + + const p2: Point = .offset(0, 10, 20); + std.debug.print("p2: ({}, {})\n", .{ p2.x, p2.y }); + } + + test "call function via decl literal" { + const p1: Point = .init(5); + try std.testing.expectEqual(5, p1.x); + try std.testing.expectEqual(5, p1.y); + + const p2: Point = .offset(0, 10, 20); + try std.testing.expectEqual(10, p2.x); + try std.testing.expectEqual(20, p2.y); + } + // #endregion decl_literal_function +}; + +const DeclLiteralErrorUnion = struct { + // #region decl_literal_error_union + const Buffer = struct { + data: std.ArrayListUnmanaged(u32), + + fn initCapacity(allocator: std.mem.Allocator, capacity: usize) !Buffer { + return .{ .data = try .initCapacity(allocator, capacity) }; + } + }; + + test "decl literal with error union" { + var buf: Buffer = try .initCapacity(std.testing.allocator, 10); + defer buf.data.deinit(std.testing.allocator); + + buf.data.appendAssumeCapacity(42); + try std.testing.expectEqual(42, buf.data.items[0]); + } + // #endregion decl_literal_error_union +}; + +const FaultyDefaultValues = struct { + // #region faulty_default_problem + /// `ptr` 指向 `[len]u32` + pub const BufferA = extern struct { + ptr: ?[*]u32 = null, + len: usize = 0, + }; + + // 看起来是空 buffer + var empty_buf: BufferA = .{}; + + // 但用户可以只覆盖部分字段,导致不一致的状态! + var bad_buf: BufferA = .{ .len = 10 }; // ptr 是 null,但 len 是 10 + // #endregion faulty_default_problem +}; + +const FaultyDefaultSolution = struct { + // #region faulty_default_solution + /// `ptr` 指向 `[len]u32` + pub const BufferB = extern struct { + ptr: ?[*]u32, + len: usize, + + // 通过声明提供预定义的有效状态 + pub const empty: BufferB = .{ .ptr = null, .len = 0 }; + }; + + // 安全地创建空 buffer + var empty_buf: BufferB = .empty; + + // 如果要手动指定值,必须同时指定所有字段 + // var custom_buf: BufferB = .{ .ptr = some_ptr, .len = 10 }; + // #endregion faulty_default_solution +}; + +const StdLibArrayList = struct { + // #region stdlib_arraylist + const Container = struct { + // 使用 .empty 而不是 .{} + list: std.ArrayListUnmanaged(i32) = .empty, + }; + + test "ArrayListUnmanaged with decl literal" { + var c: Container = .{}; + defer c.list.deinit(std.testing.allocator); + + try c.list.append(std.testing.allocator, 1); + try c.list.append(std.testing.allocator, 2); + + try std.testing.expectEqual(2, c.list.items.len); + } + // #endregion stdlib_arraylist +}; + +const StdLibDebugAllocator = struct { + // #region stdlib_debug_allocator + pub fn main() !void { + // DebugAllocator 在 0.16 中提供了 .init 声明 + var gpa: std.heap.DebugAllocator(.{}) = .init; + defer _ = gpa.deinit(); + + const allocator = gpa.allocator(); + const ptr = try allocator.alloc(u8, 100); + defer allocator.free(ptr); + + std.debug.print("allocated {} bytes\n", .{ptr.len}); + } + + test "debug allocator with decl literal" { + var gpa: std.heap.DebugAllocator(.{}) = .init; + defer _ = gpa.deinit(); + + const allocator = gpa.allocator(); + const ptr = try allocator.alloc(u8, 100); + defer allocator.free(ptr); + + try std.testing.expectEqual(100, ptr.len); + } + // #endregion stdlib_debug_allocator +}; + +const NamingConflict = struct { + // #region naming_conflict + // 错误:字段和声明同名(此代码无法编译) + // const Bad = struct { + // Value: u32, // 字段 + // const Value = 100; // 声明 - 编译错误! + // }; + + // 正确:遵循命名约定 + const Good = struct { + value: u32, // 字段使用 snake_case + const Value = 100; // 声明使用 PascalCase + }; + // #endregion naming_conflict +}; + +test "basic inference" { + const Point = BasicInference.Point; + const pt: Point = .{ .x = 10, .y = 20 }; + try std.testing.expectEqual(10, pt.x); + try std.testing.expectEqual(20, pt.y); +} + +test "decl literal basic" { + const S = DeclLiteralBasic.S; + const val: S = .default; + try std.testing.expectEqual(123, val.x); +} + +test "decl literal field default" { + const Application = DeclLiteralFieldDefault.Application; + const app1: Application = .{}; + try std.testing.expectEqual(1, app1.settings.x); +} + +test "decl literal function" { + const Point = DeclLiteralFunction.Point; + const p1: Point = .init(5); + try std.testing.expectEqual(5, p1.x); +} + +test "decl literal error union" { + const Buffer = DeclLiteralErrorUnion.Buffer; + var buf: Buffer = try .initCapacity(std.testing.allocator, 10); + defer buf.data.deinit(std.testing.allocator); + buf.data.appendAssumeCapacity(42); + try std.testing.expectEqual(42, buf.data.items[0]); +} + +test "stdlib arraylist" { + const Container = StdLibArrayList.Container; + var c: Container = .{}; + defer c.list.deinit(std.testing.allocator); + try c.list.append(std.testing.allocator, 1); + try std.testing.expectEqual(1, c.list.items.len); +} + +test "stdlib debug allocator" { + var gpa: std.heap.DebugAllocator(.{}) = .init; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + const ptr = try allocator.alloc(u8, 100); + defer allocator.free(ptr); + try std.testing.expectEqual(100, ptr.len); +} diff --git a/course/code/release/slice.zig b/course/code/release/slice.zig new file mode 100644 index 00000000..ff75aa21 --- /dev/null +++ b/course/code/release/slice.zig @@ -0,0 +1,69 @@ +pub fn main() !void { + Basic.main(); + PointerSlice.main(); + TerminatedSlice.main(); +} + +const Basic = struct { + // #region basic_more + const print = @import("std").debug.print; + + pub fn main() void { + // #region basic + var array = [_]i32{ 1, 2, 3, 4 }; + + const len: usize = 3; + const slice: []i32 = array[0..len]; + + for (slice, 0..) |ele, index| { + print("第{}个元素为:{}\n", .{ index + 1, ele }); + } + print("slice 类型为{}\n", .{@TypeOf(slice)}); + + const slice_2: []i32 = array[0..array.len]; + print("slice_2 类型为{}\n", .{@TypeOf(slice_2)}); + // #endregion basic + } + // #endregion basic_more +}; + +const PointerSlice = struct { + // #region pointer_slice_more + const print = @import("std").debug.print; + + pub fn main() void { + // #region pointer_slice + var array = [_]i32{ 1, 2, 3, 4 }; + + // 边界使用变量,保证切片不会被优化为数组指针 + var len: usize = 3; + _ = &len; + + var slice = array[0..len]; + + print("slice 类型为{}\n", .{@TypeOf(slice)}); + print("slice.ptr 类型为{}\n", .{@TypeOf(slice.ptr)}); + print("slice 的索引 0 取地址,得到指针类型为{}\n", .{@TypeOf(&slice[0])}); + // #endregion pointer_slice + } + // #endregion pointer_slice_more +}; + +const TerminatedSlice = struct { + // #region terminated_slice_more + const print = @import("std").debug.print; + + pub fn main() void { + // #region terminated_slice + // 显式声明切片类型 + const str_slice: [:0]const u8 = "hello"; + print("str_slice类型:{}\n", .{@TypeOf(str_slice)}); + + var array = [_]u8{ 3, 2, 1, 0, 3, 2, 1, 0 }; + const runtime_length: usize = 3; + const slice: [:0]u8 = array[0..runtime_length :0]; + print("slice类型:{}\n", .{@TypeOf(slice)}); + // #endregion terminated_slice + } + // #endregion terminated_slice_more +}; diff --git a/course/code/release/string.zig b/course/code/release/string.zig new file mode 100644 index 00000000..ad347839 --- /dev/null +++ b/course/code/release/string.zig @@ -0,0 +1,80 @@ +pub fn main() !void { + StringType.main(); + String.main(); + MultilineString.main(); +} + +const StringType = struct { + // #region string_type + const print = @import("std").debug.print; + pub fn main() void { + const foo = "banana"; + print("{}\n", .{@TypeOf(foo)}); + } + // #endregion string_type +}; + +const String = struct { + // #region string + const print = @import("std").debug.print; + const mem = @import("std").mem; // 用于比较字节 + + pub fn main() void { + const bytes = "hello"; + print("{}\n", .{@TypeOf(bytes)}); // *const [5:0]u8 + print("{d}\n", .{bytes.len}); // 5 + print("{c}\n", .{bytes[1]}); // 'e' + print("{d}\n", .{bytes[5]}); // 0 + print("{}\n", .{'e' == '\x65'}); // true + print("{d}\n", .{'\u{1f4a9}'}); // 128169 + print("{d}\n", .{'💯'}); // 128175 + print("{u}\n", .{'⚡'}); + print("{}\n", .{mem.eql(u8, "hello", "h\x65llo")}); // true + print("{}\n", .{mem.eql(u8, "💯", "\xf0\x9f\x92\xaf")}); // true + const invalid_utf8 = "\xff\xfe"; // 非UTF-8 字符串可以使用\xNN. + print("0x{x}\n", .{invalid_utf8[1]}); // 索引它们会返回独立的字节 + print("0x{x}\n", .{"💯"[1]}); + } + // #endregion string +}; + +const MultilineString = struct { + // #region multiline_string + const print = @import("std").debug.print; + + pub fn main() void { + const hello_world_in_c = + \\#include + \\ + \\int main(int argc, char **argv) { + \\ printf("hello world\n"); + \\ return 0; + \\} + ; + print("{s}\n", .{hello_world_in_c}); + } + // #endregion multiline_string +}; + +const PrintString = struct { + // 注意:这个不用测试,因为它本来就是错误示例 + // #region print_string_err + const std = @import("std"); + + pub fn main() void { + funnyPrint("banana"); + } + + fn funnyPrint(msg: []u8) void { + std.debug.print("*farts*, {s}", .{msg}); + } + // #endregion print_string_err +}; + +const DefineString = struct { + // #region define_string + const message_1 = "hello"; + const message_2 = [_]u8{ 'h', 'e', 'l', 'l', 'o' }; + const message_3: []const u8 = &.{ 'h', 'e', 'l', 'l', 'o' }; + // #endregion define_string +}; diff --git a/course/code/release/struct.zig b/course/code/release/struct.zig new file mode 100644 index 00000000..0baf2e22 --- /dev/null +++ b/course/code/release/struct.zig @@ -0,0 +1,489 @@ +pub fn main() !void { + Struct.main(); + StructInitBasic.main(); + SelfReference1.main(); + SelfReference2.main(); + try SelfReference3.main(); + StructInitInferred.main(); + DefaultField.main(); + EmptyStruct.main(); + Tuple_.main(); + NamePrinciple.main(); + try PackedBitOffset.main(); + try PackedCast.main(); + DestructTuple.main(); +} + +const StructAllDefault = struct { + // #region all_default + const Threshold = struct { + minimum: f32, + maximum: f32, + + // 选择声明一个默认值 + const default: Threshold = .{ + .minimum = 0.25, + .maximum = 0.75, + }; + }; + + pub fn main() !void { + const std = @import("std"); + // 初始化时直接使用默认值 + const threshold: Threshold = .default; + std.debug.print("minimum is %d, maximum is %d", .{ threshold.minimum, threshold.maximum }); + } + + // #endregion all_default +}; + +const Struct = struct { + // #region more_struct + const std = @import("std"); + + // #region default_struct + const Circle = struct { + radius: u8, + + const PI: f16 = 3.14; + + pub fn init(radius: u8) Circle { + return Circle{ .radius = radius }; + } + + fn area(self: *Circle) f16 { + return @as(f16, @floatFromInt(self.radius * self.radius)) * PI; + } + }; + // #endregion default_struct + + pub fn main() void { + const radius: u8 = 5; + var circle = Circle.init(radius); + std.debug.print("The area of a circle with radius {} is {d:.2}\n", .{ radius, circle.area() }); + } + // #endregion more_struct +}; + +const SelfReference1 = struct { + // #region more_self_reference1 + const std = @import("std"); + + // #region deault_self_reference1 + const TT = struct { + pub fn print(self: *TT) void { + _ = self; // _ 表示不使用变量 + std.debug.print("Hello, world!\n", .{}); + } + }; + // #endregion deault_self_reference1 + + pub fn main() void { + var tmp: TT = .{}; + tmp.print(); + } + // #endregion more_self_reference1 +}; + +const SelfReference2 = struct { + // #region more_self_reference2 + const std = @import("std"); + + // #region deault_self_reference2 + fn List(comptime T: type) type { + return struct { + const Self = @This(); + + items: []T, + + fn length(self: Self) usize { + return self.items.len; + } + }; + } + // #endregion deault_self_reference2 + + pub fn main() void { + const int_list = List(u8); + var arr: [5]u8 = .{ + 1, 2, 3, 4, 5, + }; + + var list: int_list = .{ + .items = &arr, + }; + + std.debug.print("list len is {}\n", .{list.length()}); + } + // #endregion more_self_reference2 +}; + +const DestructTuple = struct { + pub fn main() void { + // #region destruct_tuple + const print = @import("std").debug.print; + + var x: u32 = undefined; + var y: u32 = undefined; + var z: u32 = undefined; + + const tuple = .{ 1, 2, 3 }; + + x, y, z = tuple; + + print("tuple: x = {}, y = {}, z = {}\n", .{ x, y, z }); + // #endregion destruct_tuple + } +}; + +const SelfReference3 = struct { + // #region more_self_reference3 + const std = @import("std"); + + var gpa = std.heap.DebugAllocator(.{}){}; + + // #region deault_self_reference3 + const User = struct { + userName: []u8, + password: []u8, + email: []u8, + active: bool, + + pub const writer = "zig-course"; + + pub fn init(userName: []u8, password: []u8, email: []u8, active: bool) User { + return User{ + .userName = userName, + .password = password, + .email = email, + .active = active, + }; + } + + pub fn print(self: *User) void { + std.debug.print( + \\username: {s} + \\password: {s} + \\email: {s} + \\active: {} + \\ + , .{ + self.userName, + self.password, + self.email, + self.active, + }); + } + }; + // #endregion deault_self_reference3 + + const name = "xiaoming"; + const passwd = "123456"; + const mail = "123456@qq.com"; + + pub fn main() !void { + // 我们在这里使用了内存分配器的知识,如果你需要的话,可以提前跳到内存管理进行学习! + const allocator = gpa.allocator(); + defer { + const deinit_status = gpa.deinit(); + if (deinit_status == .leak) std.testing.expect(false) catch @panic("TEST FAIL"); + } + + const username = try allocator.alloc(u8, 20); + defer allocator.free(username); + + // @memset 是一个内存初始化函数,它会将一段内存初始化为 0 + @memset(username, 0); + // @memcpy 是一个内存拷贝函数,它会将一个内存区域的内容拷贝到另一个内存区域 + @memcpy(username[0..name.len], name); + + const password = try allocator.alloc(u8, 20); + defer allocator.free(password); + + @memset(password, 0); + @memcpy(password[0..passwd.len], passwd); + + const email = try allocator.alloc(u8, 20); + defer allocator.free(email); + + @memset(email, 0); + @memcpy(email[0..mail.len], mail); + + var user = User.init(username, password, email, true); + user.print(); + } + // #endregion more_self_reference3 +}; + +const StructInitBasic = struct { + pub fn main() void { + // #region struct_init_basic + const Point = struct { + x: i32, + y: i32, + }; + + // 使用完整的结构体字面量语法初始化 + const pt1 = Point{ .x = 10, .y = 20 }; + + // 也可以先声明类型,再使用完整语法初始化 + const pt2: Point = Point{ .x = 30, .y = 40 }; + // #endregion struct_init_basic + + _ = pt1; + _ = pt2; + } +}; + +const StructInitInferred = struct { + // #region struct_init_inferred + const Point = struct { + x: i32, + y: i32, + + // 在方法返回值中使用简写语法 + pub fn origin() Point { + return .{ .x = 0, .y = 0 }; // 返回类型已声明为 Point,可推断 + } + }; + + fn printPoint(p: Point) void { + _ = p; + } + + pub fn main() void { + // 完整语法:显式指定类型名称 + const pt1 = Point{ .x = 10, .y = 20 }; + + // 简写语法:当类型可推断时,可省略类型名称 + const pt2: Point = .{ + .x = 13, + .y = 67, + }; + + // 在方法返回值中使用简写 + const pt3 = Point.origin(); + + // 作为函数参数传递(参数类型已知时可推断) + printPoint(.{ .x = 100, .y = 200 }); + + _ = pt1; + _ = pt2; + _ = pt3; + } + // #endregion struct_init_inferred +}; + +// #region linked_list +fn LinkedList(comptime T: type) type { + return struct { + pub const Node = struct { + // 这里我们提前使用了可选类型,如有需要可以提前跳到可选类型部分学习! + prev: ?*Node, + next: ?*Node, + data: T, + }; + + first: ?*Node, + last: ?*Node, + len: usize, + }; +} +// #endregion linked_list + +const DefaultField = struct { + pub fn main() void { + // #region default_field + const Foo = struct { + a: i32 = 1234, + b: i32, + }; + + const x = Foo{ + .b = 5, + }; + // #endregion default_field + _ = x; + } +}; + +const EmptyStruct = struct { + // #region more_empty_struct + const std = @import("std"); + + // #region default_empty_struct + const Empty = struct {}; + // #endregion default_empty_struct + + pub fn main() void { + std.debug.print("{}\n", .{@sizeOf(Empty)}); + } + // #endregion more_empty_struct +}; + +const BasePtr = struct { + // #region base_ptr + const Point = struct { + x: f32, + y: f32, + }; + + fn setYBasedOnX(x: *f32, y: f32) void { + const point: Point = @fieldParentPtr("x", x); + point.y = y; + } + // #endregion base_ptr +}; + +const Tuple_ = struct { + pub fn main() void { + // #region tuple + // 我们定义了一个元组类型 + const Tuple = struct { u8, u8 }; + + // 直接使用字面量来定义一个元组 + const values = .{ + @as(u32, 1234), + @as(f64, 12.34), + true, + "hi", + }; + // 值得注意的是,values的类型和Tuple仅仅是结构相似,但不是同一类型! + // 因为values的类型是由编译器在编译期间自行推导出来的。 + + const hi = values.@"3"; // "hi" + // #endregion tuple + _ = hi; + _ = Tuple; + } +}; + +const NamePrinciple = struct { + // #region name_principle + const std = @import("std"); + + pub fn main() void { + const Foo = struct {}; + std.debug.print("variable: {s}\n", .{@typeName(Foo)}); + std.debug.print("anonymous: {s}\n", .{@typeName(struct {})}); + std.debug.print("function: {s}\n", .{@typeName(List(i32))}); + } + + fn List(comptime T: type) type { + return struct { + x: T, + }; + } + // #endregion name_principle +}; + +const PackedBitOffset = struct { + // #region packed_bit_offset + const std = @import("std"); + const expect = std.testing.expect; + + const BitField = packed struct { + a: u3, + b: u3, + c: u2, + }; + + pub fn main() !void { + // @bitOffsetOf 用于获取位域的偏移量(即偏移几位) + try expect(@bitOffsetOf(BitField, "a") == 0); + try expect(@bitOffsetOf(BitField, "b") == 3); + try expect(@bitOffsetOf(BitField, "c") == 6); + + // @offsetOf 用于获取字段的偏移量(即偏移几个字节) + try expect(@offsetOf(BitField, "a") == 0); + try expect(@offsetOf(BitField, "b") == 0); + try expect(@offsetOf(BitField, "c") == 0); + } + // #endregion packed_bit_offset +}; + +const PackedCast = struct { + // #region packed_cast + const std = @import("std"); + // 这里获取目标架构是字节排序方式,大端和小端 + const native_endian = @import("builtin").target.cpu.arch.endian(); + const expect = std.testing.expect; + + const Full = packed struct { + number: u16, + }; + + const Divided = packed struct { + half1: u8, + quarter3: u4, + quarter4: u4, + }; + + fn doTheTest() !void { + try expect(@sizeOf(Full) == 2); + try expect(@sizeOf(Divided) == 2); + + const full = Full{ .number = 0x1234 }; + const divided: Divided = @bitCast(full); + + try expect(divided.half1 == 0x34); + try expect(divided.quarter3 == 0x2); + try expect(divided.quarter4 == 0x1); + + const ordered: [2]u8 = @bitCast(full); + + switch (native_endian) { + .big => { + try expect(ordered[0] == 0x12); + try expect(ordered[1] == 0x34); + }, + .little => { + try expect(ordered[0] == 0x34); + try expect(ordered[1] == 0x12); + }, + } + } + + pub fn main() !void { + try doTheTest(); + try comptime doTheTest(); + } + // #endregion packed_cast +}; + +const aligned_struct = struct { + // #region aligned_struct + const std = @import("std"); + const expect = std.testing.expect; + + const S = packed struct { + a: u32, + b: u32, + }; + test "overaligned pointer to packed struct" { + var foo: S align(4) = .{ .a = 1, .b = 2 }; + const ptr: *align(4) S = &foo; + const ptr_to_b: *u32 = &ptr.b; + try expect(ptr_to_b.* == 2); + } + // #endregion aligned_struct +}; + +const reorder_struct = struct { + // #region reorder_struct + const std = @import("std"); + + const Foo = packed struct { + x: i32, + y: usize, // 地址大小的整数 + }; + + pub fn main() !void { + std.debug.print("{any}\n", .{@sizeOf(Foo)}); + std.debug.print("{any}\n", .{@bitSizeOf(Foo) / 8}); + + std.debug.print("{any}\n", .{@bitOffsetOf(Foo, "x") / 8}); + std.debug.print("{any}\n", .{@bitOffsetOf(Foo, "y") / 8}); + } + // #endregion reorder_struct +}; diff --git a/course/code/release/switch.zig b/course/code/release/switch.zig new file mode 100644 index 00000000..1e2fcfca --- /dev/null +++ b/course/code/release/switch.zig @@ -0,0 +1,283 @@ +pub fn main() !void { + Basic.main(); + try Advanced.main(); + Expression.main(); + Catch_tagUnion.main(); + AutoRefer.main(); + try LabeledSwitch1.main(); + try LabeledSwitch2.main(); +} + +const Basic = struct { + // #region basic_more + const std = @import("std"); + const print = std.debug.print; + + pub fn main() void { + // #region basic + const num: u8 = 5; + switch (num) { + 5 => { + print("this is 5\n", .{}); + }, + else => { + print("this is not 5\n", .{}); + }, + } + // #endregion basic + } + // #endregion basic_more +}; + +const Advanced = struct { + const std = @import("std"); + const expect = std.testing.expect; + pub fn main() !void { + // #region advanced + const a: u64 = 10; + const zz: u64 = 103; + + // 作为表达式使用 + const b = switch (a) { + // 多匹配项 + 1, 2, 3 => 0, + + // 范围匹配 + 5...100 => 1, + + // tag形式的分配匹配,可以任意复杂 + 101 => blk: { + const c: u64 = 5; + // 下一行代表返回到blk这个tag处 + break :blk c * 2 + 1; + }, + + zz => zz, + // 支持编译期运算 + blk: { + const d: u32 = 5; + const e: u32 = 100; + break :blk d + e; + } => 107, + + // else 匹配剩余的分支 + else => 9, + }; + + try expect(b == 1); + // #endregion advanced + } +}; + +const Expression = struct { + // #region expression_more + const builtin = @import("builtin"); + + pub fn main() void { + // #region expression + const os_msg = switch (builtin.target.os.tag) { + .linux => "we found a linux user", + else => "not a linux user", + }; + // #endregion expression + _ = os_msg; + } + // #endregion expression_more +}; + +const Catch_tagUnion = struct { + const std = @import("std"); + pub fn main() void { + // #region catch_tag_union + // 定义两个结构体 + const Point = struct { + x: u8, + y: u8, + }; + const Item = union(enum) { + a: u32, + c: Point, + d, + e: u32, + }; + + var a = Item{ .c = Point{ .x = 1, .y = 2 } }; + + const b = switch (a) { + // 多个匹配 + Item.a, Item.e => |item| item, + + // 可以使用 * 语法来捕获对应的指针进行修改操作 + Item.c => |*item| blk: { + item.*.x += 1; + break :blk 6; + }, + + // 这里最后一个联合类型,匹配已经穷尽了,我们就不需要使用else了 + Item.d => 8, + }; + + std.debug.print("{any}\n", .{b}); + // #endregion catch_tag_union + } +}; + +const AutoRefer = struct { + pub fn main() void { + // #region auto_refer + const Color = enum { + auto, + off, + on, + }; + const color = Color.off; + // 编译器会帮我们完成其余的工作 + const result = switch (color) { + .auto => false, + .on => false, + .off => true, + }; + // #endregion auto_refer + + _ = result; + } +}; + +// #region isFieldOptional +// 这段函数用来判断一个结构体的字段是否是 optional,同时它也是 comptime 的 +// 故我们可以在下面使用inline 来要求编译器帮我们展开这个switch +fn isFieldOptional(comptime T: type, field_index: usize) !bool { + const fields = @typeInfo(T).Struct.fields; + return switch (field_index) { + // 这里每次都是不同的值 + inline 0...fields.len - 1 => |idx| { + return @typeInfo(fields[idx].type) == .Optional; + }, + else => return error.IndexOutOfBounds, + }; +} +// #endregion isFieldOptional + +// #region withSwitch +const AnySlice = union(enum) { + a: u8, + b: i8, + c: bool, + d: []u8, +}; + +fn withSwitch(any: AnySlice) usize { + return switch (any) { + // 这里的 slice 可以匹配所有的 Anyslice 类型 + inline else => |slice| _ = slice, + }; +} +// #endregion withSwitch + +// #region catch_tag_union_value +const U = union(enum) { + a: u32, + b: f32, +}; + +fn getNum(u: U) u32 { + switch (u) { + // 这里 num 是一个运行时可知的值 + // 而 tag 则是对应的标签名,这是编译期可知的 + inline else => |num, tag| { + if (tag == .b) { + return @trunc(num); + } + return num; + }, + } +} +// #endregion catch_tag_union_value + +const LabeledSwitch1 = struct { + pub fn main() !void { + // #region labeled_switch_1 + sw: switch (@as(i32, 5)) { + 5 => continue :sw 4, + + // `continue` can occur multiple times within a single switch prong. + 2...4 => |v| { + if (v > 3) { + continue :sw 2; + } else if (v == 3) { + + // `break` can target labeled loops. + break :sw; + } + + continue :sw 1; + }, + + 1 => return, + + else => unreachable, + } + // #endregion labeled_switch_1 + } +}; + +const LabeledSwitch2 = struct { + pub fn main() !void { + // #region labeled_switch_2 + var sw: i32 = 5; + while (true) { + switch (sw) { + 5 => { + sw = 4; + continue; + }, + 2...4 => |v| { + if (v > 3) { + sw = 2; + continue; + } else if (v == 3) { + break; + } + + sw = 1; + continue; + }, + 1 => return, + else => unreachable, + } + } + // #endregion labeled_switch_2 + } +}; + +// #region vm +const Instruction = enum { + add, + mul, + end, +}; + +fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 { + const std = @import("std"); + var stack = try std.BoundedArray(i32, 8).fromSlice(initial_stack); + var ip: usize = 0; + + return vm: switch (code[ip]) { + // Because all code after `continue` is unreachable, this branch does + // not provide a result. + .add => { + try stack.append(stack.pop().? + stack.pop().?); + + ip += 1; + continue :vm code[ip]; + }, + .mul => { + try stack.append(stack.pop().? * stack.pop().?); + + ip += 1; + continue :vm code[ip]; + }, + .end => stack.pop().?, + }; +} +// #endregion vm diff --git a/course/code/release/type-cast.zig b/course/code/release/type-cast.zig new file mode 100644 index 00000000..a400d10d --- /dev/null +++ b/course/code/release/type-cast.zig @@ -0,0 +1,276 @@ +pub fn main() !void { + try tag_union_enum.main(); + try peer_resolution_2.main(); + try peer_resolution_3.main(); + try peer_resolution_4.main(); + try peer_resolution_5.main(); + try peer_resolution_7.main(); +} + +const widen = struct { + // #region widen + const a: u8 = 250; + const b: u16 = a; + const c: u32 = b; + const d: u64 = c; + const e: u64 = d; + const f: u128 = e; + // f 和 a 是相等的 + + const g: u8 = 250; + const h: i16 = h; + // g 和 h 相等 + + const i: f16 = 12.34; + const j: f32 = i; + const k: f64 = j; + const l: f128 = k; + // i 和 l 相等 + // #endregion widen +}; + +const pointer_arr_slice_1 = struct { + // #region pointer_arr_slice_1 + const x1: []const u8 = "hello"; + const x2: []const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 }; + // x1 和 x2 相等 + + const y1: anyerror![]const u8 = "hello"; + const y2: anyerror![]const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 }; + // 是错误联合类型时,也有效 + + const z1: ?[]const u8 = "hello"; + const z2: ?[]const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 }; + // 可选类型也有效果 + + const a1: anyerror!?[]const u8 = "hello"; + const a2: anyerror!?[]const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 }; + // 错误联合可选类型也有效 + // #endregion pointer_arr_slice_1 +}; + +const pointer_arr_slice_2 = struct { + // #region pointer_arr_slice_2 + var buf: [5]u8 = "hello".*; + const x: []u8 = &buf; + + const buf2 = [2]f32{ 1.2, 3.4 }; + const x2: []const f32 = &buf2; + // #endregion pointer_arr_slice_2 +}; + +const pointer_arr_slice_3 = struct { + // #region pointer_arr_slice_3 + var buf: [5]u8 = "hello".*; + const x: [*]u8 = &buf; + + var buf2: [5]u8 = "hello".*; + const x2: ?[*]u8 = &buf2; + // 可选类型也有效 + + var buf3: [5]u8 = "hello".*; + const x3: anyerror![*]u8 = &buf3; + // 联合错误类型也有效 + + var buf4: [5]u8 = "hello".*; + const x4: anyerror!?[*]u8 = &buf4; + // 联合错误可选类型也有效 + // #endregion pointer_arr_slice_3 +}; + +const pointer_arr_slice_4 = struct { + // #region pointer_arr_slice_4 + var x: i32 = 1234; + const y: *[1]i32 = &x; + const z: [*]i32 = y; + // 先转为长度为 1 的数组指针,再转换为多项指针。 + // 如果 x 直接赋值给 z,则编译器会报错 + // #endregion pointer_arr_slice_4 +}; + +const optional_payload = struct { + // #region optional_payload + const y: ?i32 = null; + const y1: anyerror!?i32 = null; + // 错误联合可选类型也可以 + // #endregion optional_payload + + // #region error_union + const z: anyerror!i32 = error.Failure; + // #endregion error_union +}; + +const comptime_integer = struct { + // #region comptime_integer + const x: u64 = 255; + const y: u8 = x; + // 自动转换到 u8 + // #endregion comptime_integer +}; + +const tag_union_enum = struct { + // #region tag_union_enum + const std = @import("std"); + const expect = std.testing.expect; + + const E = enum { + one, + two, + three, + }; + + const U = union(E) { + one: i32, + two: f32, + three, + }; + + const U2 = union(enum) { + a: void, + b: f32, + + fn tag(self: U2) usize { + switch (self) { + .a => return 1, + .b => return 2, + } + } + }; + + pub fn main() !void { + const u = U{ .two = 12.34 }; + const e: E = u; // 将联合类型转换为枚举 + try expect(e == E.two); + + const three = E.three; + // 将枚举转换为联合类型,注意这里 three 并没有对应的类型,故可以直接转换 + const u_2: U = three; + try expect(u_2 == E.three); + + const u_3: U = .three; // 字面量供 zig 编译器来自动推导 + try expect(u_3 == E.three); + + const u_4: U2 = .a; // 字面量供 zig 编译器来推导,a 也是没有对应的类型(void) + try expect(u_4.tag() == 1); + + // 下面的 b 字面量推导是错误的,因为它有对应的类型 f32 + //var u_5: U2 = .b; + //try expect(u_5.tag() == 2); + } + // #endregion tag_union_enum +}; + +const tuple_arr = struct { + // #region tuple_arr + const Tuple = struct { u8, u8 }; + + const tuple: Tuple = .{ 5, 6 }; + // 一切都是自动完成的 + const array: [2]u8 = tuple; + // #endregion tuple_arr +}; + +const peer_resolution_1 = struct { + // #region peer_resolution_1 + const a: i8 = 12; + const b: i16 = 34; + const c = a + b; + // c 的类型是 u16 + // #endregion peer_resolution_1 +}; + +const peer_resolution_2 = struct { + // #region peer_resolution_2 + const std = @import("std"); + const expect = std.testing.expect; + const mem = std.mem; + + pub fn main() !void { + // mem.eql 执行检查内存是否相等 + try expect(mem.eql(u8, boolToStr(true), "true")); + try expect(mem.eql(u8, boolToStr(false), "false")); + try comptime expect(mem.eql(u8, boolToStr(true), "true")); + try comptime expect(mem.eql(u8, boolToStr(false), "false")); + } + + fn boolToStr(b: bool) []const u8 { + return if (b) "true" else "false"; + } + // #endregion peer_resolution_2 +}; + +const peer_resolution_3 = struct { + // #region peer_resolution_3 + const std = @import("std"); + const expect = std.testing.expect; + const mem = std.mem; + + pub fn main() !void { + try testPeerResolveArrayConstSlice(true); + // 上面这个语句执行会成功 + } + + fn testPeerResolveArrayConstSlice(b: bool) !void { + const value1 = if (b) "aoeu" else @as([]const u8, "zz"); + const value2 = if (b) @as([]const u8, "zz") else "aoeu"; + try expect(mem.eql(u8, value1, "aoeu")); + try expect(mem.eql(u8, value2, "zz")); + } + // #endregion peer_resolution_3 +}; + +const peer_resolution_4 = struct { + // #region peer_resolution_4 + pub fn main() !void { + // 下面语句执行为 true + _ = peerTypeTAndOptionalT(true, false).? == 0; + } + fn peerTypeTAndOptionalT(c: bool, b: bool) ?usize { + if (c) { + return if (b) null else @as(usize, 0); + } + + return @as(usize, 3); + } + // #endregion peer_resolution_4 +}; + +const peer_resolution_5 = struct { + // #region peer_resolution_5 + fn peerTypeEmptyArrayAndSlice(a: bool, slice: []const u8) []const u8 { + if (a) { + return &[_]u8{}; + } + + return slice[0..1]; + } + + pub fn main() !void { + // 以下两句均为true + _ = peerTypeEmptyArrayAndSlice(true, "hi").len == 0; + _ = peerTypeEmptyArrayAndSlice(false, "hi").len == 1; + } + // #endregion peer_resolution_5 +}; + +const peer_resolution_6 = struct { + // #region peer_resolution_6 + fn peerTypeEmptyArrayAndSliceAndError(a: bool, slice: []u8) anyerror![]u8 { + if (a) { + return &[_]u8{}; + } + + return slice[0..1]; + } + // #endregion peer_resolution_6 +}; + +const peer_resolution_7 = struct { + pub fn main() !void { + // #region peer_resolution_7 + const a: *const usize = @ptrFromInt(0x123456780); + const b: ?*usize = @ptrFromInt(0x123456780); + _ = a == b; // 这个表达式的值为 true + // #endregion peer_resolution_7 + } +}; diff --git a/course/code/release/union.zig b/course/code/release/union.zig new file mode 100644 index 00000000..be251e64 --- /dev/null +++ b/course/code/release/union.zig @@ -0,0 +1,136 @@ +pub fn main() !void { + try Basic.main(); + try Tag.main(); + try CapturePayload.main(); + TagName.main(); +} + +const Basic = struct { + // #region more_basic + const print = @import("std").debug.print; + + // #region default_basic + const Payload = union { + int: i64, + float: f64, + boolean: bool, + }; + + pub fn main() !void { + var payload = Payload{ .int = 1234 }; + payload = Payload{ .int = 9 }; + // var payload_1: Payload = .{ .int = 1234 }; + + print("{}\n", .{payload.int}); + } + // #endregion default_basic + // #endregion more_basic +}; + +const UnionInit = struct { + // #region union_init + const Payload = union { + int: i64, + float: f64, + boolean: bool, + }; + // 通过 @unionInit 初始化一个联合类型 + const payload = @unionInit(Payload, "int", 666); + // #endregion union_init +}; + +const Tag = struct { + // #region more_tag + const std = @import("std"); + const expect = std.testing.expect; + + pub fn main() !void { + // #region default_tag + // 一个枚举,用于给联合类型挂上标记 + const ComplexTypeTag = enum { + ok, + not_ok, + }; + + // 带标记的联合类型 + const ComplexType = union(ComplexTypeTag) { + ok: u8, + not_ok: void, + }; + + const c = ComplexType{ .ok = 42 }; + // 可以直接将标记联合类型作为枚举来使用,这是合法的 + try expect(@as(ComplexTypeTag, c) == ComplexTypeTag.ok); + + // 使用 switch 进行匹配 + switch (c) { + ComplexTypeTag.ok => |value| try expect(value == 42), + ComplexTypeTag.not_ok => unreachable, + } + + // 使用 zig 的 meta 库获取对应的 tag + try expect(std.meta.Tag(ComplexType) == ComplexTypeTag); + // #endregion default_tag + } + // #endregion more_tag +}; + +const CapturePayload = struct { + // #region more_capture_payload + const std = @import("std"); + const expect = std.testing.expect; + + pub fn main() !void { + // #region default_capture_payload + // 枚举,用于给联合类型打上标记 + const ComplexTypeTag = enum { + ok, + not_ok, + }; + + // 带标记的联合类型 + const ComplexType = union(ComplexTypeTag) { + ok: u8, + not_ok: void, + }; + + var c = ComplexType{ .ok = 42 }; + + // 使用 switch 进行匹配 + switch (c) { + // 捕获了标记联合值的指针,用于修改值 + ComplexTypeTag.ok => |*value| value.* += 1, + ComplexTypeTag.not_ok => unreachable, + } + + try expect(c.ok == 43); + // #endregion default_capture_payload + } + // #endregion more_capture_payload +}; + +const TagName = struct { + pub fn main() void { + // #region tag_name + const Small2 = union(enum) { + a: i32, + b: bool, + c: u8, + }; + + const name = @tagName(Small2.a); + // 这个返回值将会是 a + // #endregion tag_name + _ = name; + } +}; + +// #region auto_infer +const Number = union { + int: i32, + float: f64, +}; + +// 自动推断 +const i: Number = .{ .int = 42 }; +// #endregion auto_infer diff --git a/course/code/release/unit_test.zig b/course/code/release/unit_test.zig new file mode 100644 index 00000000..acbe65a2 --- /dev/null +++ b/course/code/release/unit_test.zig @@ -0,0 +1,82 @@ +pub fn main() !void {} + +const Basic = struct { + const std = @import("std"); + + test "expect addOne adds one to 41" { + + // 标准库提供了不少有用的函数 + // testing 下的函数均是测试使用的 + // expect 会假定其参数为 true,如果不通过则报告错误 + // try 用于当 expect 返回错误时,直接返回,并通知测试运行器测试结果未通过 + try std.testing.expect(addOne(41) == 42); + } + + test addOne { + // test 的名字也可以使用标识符,例如我们在这里使用的就是函数名字 addOne + try std.testing.expect(addOne(41) == 42); + } + + /// 定义一个函数效果是给传入的参数执行加一操作 + fn addOne(number: i32) i32 { + return number + 1; + } +}; + +const Nestd = struct { + const std = @import("std"); + const expect = std.testing.expect; + + test { + std.testing.refAllDecls(S); + _ = S; + _ = U; + } + + const S = struct { + test "S demo test" { + try expect(true); + } + + const SE = enum { + V, + + // 此处测试由于未被引用,将不会执行. + test "This Test Won't Run" { + try expect(false); + } + }; + }; + + const U = union { // U 被顶层测试块引用了 + s: US, // 并且US在此处被引用,则US容器中的测试块也会被执行测试 + + const US = struct { + test "U.US demo test" { + // This test is a top-level test declaration for the struct. + // The struct is nested (declared) inside of a union. + try expect(true); + } + }; + + test "U demo test" { + try expect(true); + } + }; +}; + +test "all" { + _ = Basic; +} + +const allDecl = struct { + const std = @import("std"); + const builtin = @import("builtin"); + + pub fn refAllDecls(comptime T: type) void { + if (!builtin.is_test) return; + inline for (comptime std.meta.declarations(T)) |decl| { + _ = &@field(T, decl.name); + } + } +}; diff --git a/course/code/release/unreachable.zig b/course/code/release/unreachable.zig new file mode 100644 index 00000000..95ccf4e4 --- /dev/null +++ b/course/code/release/unreachable.zig @@ -0,0 +1,9 @@ +pub fn main() !void { + // #region unreachable + const x = 1; + const y = 2; + if (x + y != 3) { + unreachable; + } + // #endregion unreachable +} diff --git a/course/code/release/vector.zig b/course/code/release/vector.zig new file mode 100644 index 00000000..993ab895 --- /dev/null +++ b/course/code/release/vector.zig @@ -0,0 +1,134 @@ +pub fn main() !void { + Basic.main(); + Splat.main(); + Reduce.main(); + Shuffle.main(); + Select.main(); +} + +const Basic = struct { + // #region basic + const std = @import("std"); + const print = std.debug.print; + + pub fn main() void { + const ele_4 = @Vector(4, i32); + + // 向量必须拥有编译期已知的长度和类型 + const a = ele_4{ 1, 2, 3, 4 }; + const b = ele_4{ 5, 6, 7, 8 }; + + // 执行相加的操作 + const c = a + b; + + print("Vector c is {any}\n", .{c}); + // 以数组索引的语法来访问向量的元素 + print("the third element of Vector c is {}\n", .{c[2]}); + + // 定义一个数组,注意我们这里使用的是浮点类型 + var arr1: [4]f32 = [_]f32{ 1.1, 3.2, 4.5, 5.6 }; + // 直接转换成为一个向量 + const vec: @Vector(4, f32) = arr1; + + print("Vector vec is {any}\n", .{vec}); + + // 将一个切片转换为向量 + const vec2: @Vector(2, f32) = arr1[1..3].*; + print("Vector vec2 is {any}\n", .{vec2}); + } + // #endregion basic +}; + +const Splat = struct { + pub fn main() void { + // #region splat + const scalar: u32 = 5; + const result: @Vector(4, u32) = @splat(scalar); + // #endregion splat + _ = result; + } +}; + +const Deconstruct = struct { + // #region deconstruct + const print = @import("std").debug.print; + + pub fn unpack(x: @Vector(4, f32), y: @Vector(4, f32)) @Vector(4, f32) { + const a, const c, _, _ = x; + const b, const d, _, _ = y; + return .{ a, b, c, d }; + } + + pub fn main() void { + const x: @Vector(4, f32) = .{ 1.0, 2.0, 3.0, 4.0 }; + const y: @Vector(4, f32) = .{ 5.0, 6.0, 7.0, 8.0 }; + print("{}", .{unpack(x, y)}); + } + // #endregion deconstruct +}; + +const Reduce = struct { + const std = @import("std"); + const print = std.debug.print; + + pub fn main() void { + // #region reduce + const V = @Vector(4, i32); + const value = V{ 1, -1, 1, -1 }; + + const result = value > @as(V, @splat(0)); + // result 是 { true, false, true, false }; + + const is_all_true = @reduce(.And, result); + // is_all_true 是 false + // #endregion reduce + print("is_all_true is {}\n", .{is_all_true}); + } +}; +const Shuffle = struct { + const std = @import("std"); + const print = std.debug.print; + + pub fn main() void { + //#region shuffle + const a = @Vector(7, u8){ 'o', 'l', 'h', 'e', 'r', 'z', 'w' }; + const b = @Vector(4, u8){ 'w', 'd', '!', 'x' }; + + const mask1 = @Vector(5, i32){ 2, 3, 1, 1, 0 }; + const res1: @Vector(5, u8) = @shuffle(u8, a, undefined, mask1); + // res1 的值是 hello + + // Combining two vectors + const mask2 = @Vector(6, i32){ -1, 0, 4, 1, -2, -3 }; + const res2: @Vector(6, u8) = @shuffle(u8, a, b, mask2); + // res2 的值是 world! + //#endregion shuffle + _ = res1; + _ = res2; + } +}; + +const Select = struct { + pub fn main() void { + + //#region select + const ele_4 = @Vector(4, i32); + + // 向量必须拥有编译期已知的长度和类型 + const a = ele_4{ 1, 2, 3, 4 }; + const b = ele_4{ 5, 6, 7, 8 }; + + const pred = @Vector(4, bool){ + true, + false, + false, + true, + }; + + const c = @select(i32, pred, a, b); + // c 是 { 1, 6, 7, 4 } + //#endregion select + + _ = c; + } +}; diff --git a/course/engineering/build-system.md b/course/engineering/build-system.md index bdaff7de..730c1cc0 100644 --- a/course/engineering/build-system.md +++ b/course/engineering/build-system.md @@ -35,14 +35,14 @@ Zig 使用 `build.zig` 文件来描述一个项目的构建步骤。 `Step` 会在下一小节中会重点讲述,这里介绍一下上面这个构建文件的其他部分: -- `b.standardTargetOptions`: 允许构建器读取来自命令行参数的**构建目标三元组**。 +- `b.standardTargetOptions`: 允许构建器读取来自命令行参数的目标配置,并返回可直接传给模块 `.target` 的 `ResolvedTarget`。 - `b.standardOptimizeOption`:允许构建器读取来自命令行参数的**构建优化模式**。 -- `b.addExecutable`:创建一个 [`Build.Step.Compile`](https://ziglang.org/documentation/master/std/#std.Build.Step.Compile) 并返回对应的指针,其参数为 [`std.Build.ExecutableOptions`](https://ziglang.org/documentation/master/std/#std.Build.ExecutableOptions)。 -- `b.path`:该函数用于指定获取当前项目的源文件路径,请勿手动为 `root_source_file` 赋值! +- `b.addExecutable`:创建一个 [`Build.Step.Compile`](https://ziglang.org/documentation/master/std/#std.Build.Step.Compile) 并返回对应的指针,Zig 0.16 中通常通过 `root_module = b.createModule(...)` 指定入口模块。 +- `b.path`:该函数会返回相对当前包根目录的 `LazyPath`,常用于模块的 `root_source_file`。 ::: info 🅿️ 提示 -标准构建会产生两个目录,一个是 `zig-cache`、一个是 `zig-out`,第一个是缓存目录(这有助于加快下次构建),第二个是安装目录,不是由项目决定,而是由用户决定(通过 `zig build --prefix` 参数),默认为 `zig-out`。 +标准构建会产生两个目录,一个是 `.zig-cache`、一个是 `zig-out`,第一个是缓存目录(这有助于加快下次构建),第二个是安装目录,不是由项目决定,而是由用户决定(通过 `zig build --prefix` 参数),默认为 `zig-out`。 ::: @@ -94,7 +94,7 @@ zig 提供了四种构建模式(**Build Mode**): | Debug | ReleaseFast | ReleaseSafe | ReleaseSmall | | -------------- | -------------- | -------------- | -------------- | | 构建速度很快 | 构建速度慢 | 构建速度慢 | 构建速度慢 | -| 启用安全检查 | 启用安全检查 | 启用安全检查 | 禁用安全检查 | +| 启用安全检查 | 禁用安全检查 | 启用安全检查 | 禁用安全检查 | | 较差的运行效率 | 很好的运行效率 | 中等的运行效率 | 中等的运行效率 | | 二进制体积大 | 二进制体积大 | 二进制体积大 | 二进制体积小 | | 无复现构建 | 可复现构建 | 可复现构建 | 可复现构建 | @@ -150,7 +150,7 @@ Project-Specific Options: <<<@/code/release/build_system/lib/build.zig -对应地,如果要构建动态库可以使用 `b.addSharedLibrary`。 +对应地,如果要构建动态库,可以继续使用 `b.addLibrary` 并设置 `.linkage = .dynamic`。 通常,二进制可执行程序的构建结果会输出在 `zig-out/bin` 下,而链接库的构建结果会输出在 `zig-out/lib` 下。 @@ -204,13 +204,13 @@ zig 本身提供了一个实验性的文档生成器,它支持搜索查询, 关于所有的 target,可以使用 `zig targets` 查看。 -最常用的一个 target 设置可能是 `b.standardTargetOptions`,它会允许读取命令行输入来决定构建目标 target,它返回一个 [`ResolvedTarget`](https://ziglang.org/documentation/master/std/#std.Build.ResolvedTarget)。 +最常用的一个 target 设置可能是 `b.standardTargetOptions`,它会允许读取命令行输入来决定构建目标 target,并返回一个 [`ResolvedTarget`](https://ziglang.org/documentation/master/std/#std.Build.ResolvedTarget)。在 Zig 0.16 的模块化构建 API 中,这个值通常传给模块的 `.target` 字段。 -如果需要手动指定一个 target,可以手动构建一个 `std.Target.Query` 传递给构建(`addExecutable` 和 `addStaticLibrary` 等),如: +如果需要手动指定一个 target,可以先构建一个 `std.Target.Query`,再通过 `b.resolveTargetQuery` 得到 `ResolvedTarget`,并把解析后的结果传给模块,如: <<<@/code/release/build_system/build.zig#crossTarget -值得注意的是,目前 zig 已经将 `target query` 和 `resolved target` 完全分开,如果要手动指定构建目标,需要先创建一个 `Query`,再使用 `b.resolveTargetQuery` 进行解析。 +值得注意的是,目前 zig 已经将 `target query` 和 `resolved target` 完全分开;`Query` 只描述目标,真正传给 `createModule` 的 `.target` 应是 `ResolvedTarget`。 关于该部分的变动可以参考此处的 PR:[Move many settings from being per-Compilation to being per-Module](https://github.com/ziglang/zig/pull/18160). diff --git a/course/engineering/package_management.md b/course/engineering/package_management.md index 2206feb1..1f201eba 100644 --- a/course/engineering/package_management.md +++ b/course/engineering/package_management.md @@ -18,13 +18,13 @@ zig 当前并没有一个中心化存储库,包可以来自任何来源,无 以上字段含义为: -- `name`:当前你所开发的包的名字 +- `name`:当前你所开发的包的名字,Zig 0.16 起应写成 enum literal,例如 `.importer`。 - `version`:包的版本,使用 [Semantic Version](https://semver.org/)。 -- `fingerprint`: 该值为校验和,它与包的名字有关,使用 `zig build` 时会告诉你应该填什么。 +- `fingerprint`: 包的身份指纹,Zig 0.16 起为必填项;它不同于依赖的 `hash`,会和 `name` 一起用于识别同一个项目的不同版本或本地 fork,缺失时 `zig build` 会报错并提示应填写的值。 - `dependencies`:依赖项,内部是一个匿名结构体;每个字段名就是依赖包名,字段值则是该依赖的配置。 常见配置项包括 `url`、`hash` 和 `path`。 - 当使用源码包时填写 `url` 和 `hash`;当使用本地目录时填写 `path`,两种方式不能混用。 -- `paths`:显式声明包含的源文件,包含所有则指定为空。 + 当使用远程源码包时填写 `url` 和 `hash`;当使用本地包时填写 `path`,两种方式不能混用。 +- `paths`:显式声明会被打包和参与 hash 的文件或目录,例如 `src`、`build.zig` 和 `build.zig.zon`;不在列表中的文件会被过滤掉,需要包含整个包时可写 `"."`。 ::: info 🅿️ 提示 @@ -48,6 +48,8 @@ zig 当前并没有一个中心化存储库,包可以来自任何来源,无 目前 zig 已支持通过 [`zig fetch`](../environment/zig-command#zig-fetch) 来获取 hash 并写入到 `.zon` 中! +Zig 0.16 会把抓取到的依赖放在项目根目录旁的 `zig-pkg` 目录,通常不需要提交进仓库。 + ::: ## 编写包 @@ -74,7 +76,7 @@ pub fn addModule( <<<@/code/release/package_management_exporter/build.zig#create_module -这就是一个最基本的包暴露实现,指定了包名和包的入口源文件地址(`b.path` 是相对当前项目路径取 `Path`),通过 `addModule` 函数暴露的模块是完全公开的。 +这就是一个最基本的包暴露实现,指定了包名和包的入口源文件地址(`b.path` 会按当前包根目录解析并返回 `LazyPath`),通过 `addModule` 函数暴露的模块是完全公开的。 ::: info 🅿️ 提示 @@ -98,6 +100,6 @@ fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency ::: info 🅿️ 提示 -`dependency` 包含一个额外的参数 `args`,这是传给对应的包构建的参数(类似在命令行构建时使用的 `-D` 参数,通常是我们使用 `b.options` 获取,通过 [`std.Build.option`](https://ziglang.org/documentation/master/std/#std.Build.option) 实现),当前包的参数并不会向包传递,需要手动显式指定转发。 +`dependency` 包含一个额外的参数 `args`,这是传给对应的包构建的参数(类似在命令行构建时使用的 `-D` 参数,通常是我们使用 `b.option` 获取,通过 [`std.Build.option`](https://ziglang.org/documentation/master/std/#std.Build.option) 实现),当前包的参数并不会向包传递,需要手动显式指定转发。 ::: diff --git a/course/environment/editor.md b/course/environment/editor.md index 12a3d981..c16e1b01 100644 --- a/course/environment/editor.md +++ b/course/environment/editor.md @@ -110,9 +110,11 @@ zls 已支持保存时自动检查代码的功能,但此功能默认关闭。 ```zig const exe_check = b.addExecutable(.{ .name = "foo", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), }); const check = b.step("check", "Check if foo compiles"); diff --git a/course/environment/zig-command.md b/course/environment/zig-command.md index b3af5857..e62567f5 100644 --- a/course/environment/zig-command.md +++ b/course/environment/zig-command.md @@ -84,7 +84,7 @@ outline: deep ```sh # 仅获取包的哈希值 $ zig fetch https://github.com/webui-dev/zig-webui/archive/main.tar.gz -12202809180bab2c7ae3382781b2fc65395e74b49d99ff2595f3fea9f7cf66cfa963 +# 输出形如:包名-版本-哈希(以实际命令为准) ``` 如果你希望将包直接添加为依赖项,可以附加 `--save` 参数: diff --git a/course/examples/echo_tcp_server.md b/course/examples/echo_tcp_server.md index 9a403d37..11ccdbbe 100644 --- a/course/examples/echo_tcp_server.md +++ b/course/examples/echo_tcp_server.md @@ -18,17 +18,13 @@ Socket(套接字)是计算机网络中用于实现不同计算机或同一 除了常见的 **TCP** 和 **UDP** 外,还有一种叫做 **Unix Socket**,用于在同一台机器上的不同进程间进行通信,并不使用网络协议栈,而是直接在内核中传递数据,比 TCP 和 UDP 更加高效。 -### IO 多路复用 +### Zig 0.16 的 `std.Io` -**I/O 多路复用**是一种允许一个进程同时监视多个 I/O 通道(例如,_socket_、*文件描述符*等),并知道哪个通道可以进行读写操作的技术。这样,一个进程就可以同时处理多个 I/O 操作,而无需为每个 I/O 操作启动一个新的线程或进程。 - -> I/O 多路复用的主要优点是提高了程序的效率。如果没有 I/O 多路复用,程序可能需要为每个 I/O 操作创建一个新的线程或进程,这会消耗大量的系统资源。通过使用 I/O 多路复用,程序可以在一个单独的线程或进程中处理多个 I/O 操作,从而减少了系统资源的使用。 - -I/O 多路复用的常见实现包括 select、poll 和 epoll 等系统调用。这些系统调用允许程序指定一个文件描述符列表,并等待其中任何一个文件描述符准备好进行 I/O 操作。当一个或多个文件描述符准备好时,系统调用返回,程序就可以进行相应的读或写操作。 +Zig 0.16 的网络示例优先使用标准库的 `std.Io` 接口。`std.Io.Threaded` 提供 I/O 后端;本例使用单线程模式,配合 `std.Io.net` 完成监听、接受连接、读写和关闭。 ## 思路讲解 -目标:实现一个单线程基于 `poll` 的 **echo server**。 +目标:使用 `std.Io` 实现一个简单的单线程、单客户端 **echo server**。 常规的 socket 编程流程为: @@ -42,42 +38,20 @@ I/O 多路复用的常见实现包括 select、poll 和 epoll 等系统调用。 ![tcp](../picture/echo_tcp_server/tcp.drawio.png) -以上是一个常规的 TCP server 的运作图,但是缺点也很明显,那就是这样运行的话 server 一次只能处理一个连接,无法实现并发连接。 - -故我们引入 `poll`,它是 POSIX 标准之一,允许我们通知内核替我们监听多个描述符(此处指代 socket 描述符),以一种订阅的方案来监听一组描述符,直到描述符可读或写时通知进程就绪的描述符数量。 - -::: info 🅿️ 提示 - -严格来说,**poll** 已经算是一门“过时”的技术,在 linux 平台它被 **epoll** 取代,BSD 系统(包括 mac)则使用 **kqueue**,而 windows 使用 **IOCP(I/O Completion Ports)** 和 **Overlapped I/O**。 - -WSAPoll For Windows: [WSAPoll function](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll) - -Poll For Linux: [poll(2) — Linux manual page](https://man7.org/linux/man-pages/man2/poll.2.html) - -::: - -以下是使用 `poll` 后的运作图: - -![tcp](../picture/echo_tcp_server/tcp_poll.drawio.png) +上图就是本例采用的流程:服务器每次 `accept` 一个客户端,在同一线程内读取数据并写回,客户端断开后再等待下一个连接。因此它是一个串行示例,不是并发服务器。 ## 实战 -为了同时兼容 linux 和 windows,我们需要利用一下 zig 的 `builtin` 包来判断构建目标来决定使用的函数(poll 在 windows 上的实现不完全标准)。 +代码使用 `std.Io.Threaded` 的单线程后端和 `std.Io.net`,监听本机 `8080` 端口。完整的代码在 [Github](https://github.com/zigcc/zig-course/tree/main/course/code/release/echo_tcp_server.zig),测试用的客户端可以使用 _telnet_(windows、linux、mac 均可用)。 -完整的代码在 [Github](https://github.com/zigcc/zig-course/tree/main/course/code/release/echo_tcp_server.zig),测试用的客户端可以使用 _telnet_(windows、linux、mac 均可用)。 - -_server_ 监听端口的实现: +初始化 I/O 后端并监听端口的实现: <<< @/code/release/echo_tcp_server.zig#listen -定义一些必要的数据: +等待新客户端连接的实现: -<<< @/code/release/echo_tcp_server.zig#data +<<< @/code/release/echo_tcp_server.zig#new-connection -处理客户端发送的数据的实现: +处理当前客户端数据的实现: <<< @/code/release/echo_tcp_server.zig#exist-connections - -处理新连接的实现: - -<<< @/code/release/echo_tcp_server.zig#new-connection diff --git a/course/update/0.16.0-description.md b/course/update/0.16.0-description.md index 542bdf06..68dd28c6 100644 --- a/course/update/0.16.0-description.md +++ b/course/update/0.16.0-description.md @@ -95,10 +95,12 @@ Zig 把对各目标的支持程度划成四档(Tier 1 最高),具体含义 这次发布继续收紧了位级布局和 ABI 边界的隐式行为: -- `packed union` 现在要求更明确的 backing integer 语义 +- `packed union` 现在要求明确的 backing integer 语义:不允许 unused bits,且字段 bit size 必须与 backing integer 一致 - `packed struct` / `packed union` 不再允许直接放指针字段 - `extern` 场景下,`enum` 与 `packed` 类型不能再依赖隐式推断的底层整数类型 +换句话说,`packed union(T)` 不能再留下 padding / unused bits;每个字段的 `@bitSizeOf` 都必须等于 backing integer `T` 的 `@bitSizeOf`。 + 从设计上看,这些限制的方向非常统一:**凡是会影响 ABI 或精确内存布局的内容,Zig 都更倾向于要求你显式写出来。** ### 向量语义进一步收紧 @@ -335,7 +337,7 @@ pub fn main(init: std.process.Init) !void { 围绕新的 `std.Io`,标准库的并发相关设施也继续收敛: -- `std.Thread.Pool` 被移除,官方建议迁移到 `std.Io.async` / `std.Io.Group.async` +- `std.Thread.Pool` 被移除;简单的 `spawnWg` / group-await 模式可迁到 `std.Io.async` / `std.Io.Group.async`,复杂同步或必须保证并发执行的场景需参考 `std.Io.concurrent` 文档 - `std.heap.ArenaAllocator` 变成了 thread-safe 且 lock-free - `std.heap.ThreadSafeAllocator` 被移除 diff --git a/course/update/upgrade-0.16.0.md b/course/update/upgrade-0.16.0.md index 6fbd0c7f..fca691c3 100644 --- a/course/update/upgrade-0.16.0.md +++ b/course/update/upgrade-0.16.0.md @@ -104,7 +104,7 @@ const exe = b.addExecutable(.{ const c = @import("c"); ``` -这样翻译出来的 C 代码与以前用 `@cImport` 的结果是一致的;如果需要更细的翻译参数控制,可以把官方 `translate-c` 包作为显式依赖加入。 +这样翻译出来的 C 代码与以前用 `@cImport` 的结果是一致的;如果需要更细的[翻译参数控制](https://codeberg.org/ziglang/translate-c/src/commit/41c10fa66ac81343c33f2b8c746f181b41eaaa27/build/Translator.zig#L40),可以把官方 [`translate-c`](https://codeberg.org/ziglang/translate-c/src/commit/41c10fa66ac81343c33f2b8c746f181b41eaaa27/build/Translator.zig#L40) 包作为显式依赖加入。 如果你升级到 `0.16.0` 后发现同一份 C 头文件翻译结果和以前不一致,也不用急着怀疑自己。因为 `translate-c` 的底层实现已经从 `libclang` 切换到了 Aro,这类差异更应该视为 bug 并反馈给 Zig。 @@ -123,6 +123,52 @@ const c = @import("c"); - `@Union()` - `@Enum()` +```zig +@EnumLiteral() type + +@Int(comptime signedness: std.builtin.Signedness, comptime bits: u16) type + +@Tuple(comptime field_types: []const type) type + +@Pointer( + comptime size: std.builtin.Type.Pointer.Size, + comptime attrs: std.builtin.Type.Pointer.Attributes, + comptime Element: type, + comptime sentinel: ?Element, +) type + +@Fn( + comptime param_types: []const type, + comptime param_attrs: *const [param_types.len]std.builtin.Type.Fn.Param.Attributes, + comptime ReturnType: type, + comptime attrs: std.builtin.Type.Fn.Attributes, +) type + +@Struct( + comptime layout: std.builtin.Type.ContainerLayout, + comptime BackingInt: ?type, + comptime field_names: []const []const u8, + comptime field_types: *const [field_names.len]type, + comptime field_attrs: *const [field_names.len]std.builtin.Type.StructField.Attributes, +) type + +@Union( + comptime layout: std.builtin.Type.ContainerLayout, + /// Either the integer tag type, or the integer backing type, depending on `layout`. + comptime ArgType: ?type, + comptime field_names: []const []const u8, + comptime field_types: *const [field_names.len]type, + comptime field_attrs: *const [field_names.len]std.builtin.Type.UnionField.Attributes, +) type + +@Enum( + comptime TagInt: type, + comptime mode: std.builtin.Type.Enum.Mode, + comptime field_names: []const []const u8, + comptime field_values: *const [field_names.len]TagInt, +) type +``` + 常见迁移: ```zig @@ -339,27 +385,50 @@ const MyStruct = @Struct(.auto, null, std.meta.fieldNames(MyEnum), &@splat(Field 需要注意的是,**这套新 builtin 里没有 `@Float`**——因为运行时浮点类型只有 5 种,在用户代码里实现这件事很轻松;如果非要从位数构造浮点类型,可以用 `std.meta.Float`。 +这套新 builtin 里也没有下面这些类型构造函数: + +- 没有 `@Array`:直接用普通数组语法即可。一个通用 `Array` 函数可以写成这样: + + ```zig + fn Array(comptime len: usize, comptime Elem: type, comptime sentinel: ?Elem) type { + return if (sentinel) |s| [len:s]Elem else [len]Elem; + } + ``` + + 实际使用时通常不需要这么泛化,直接把调用点替换为 `[len]Elem` 或 `[len:s]Elem` 即可。 + +- 没有 `@Opaque`:直接写 `opaque {}`。 +- 没有 `@Optional`:直接写 `?T`。 +- 没有 `@ErrorUnion`:直接写 `E!T`。 +- 没有 `@ErrorSet`:为了简化语言,不再支持 reify error set。请用 `error{ ... }` 语法显式声明 error set。 + 如果你的项目大量依赖元编程,这一项往往是升级时最先爆出来的报错来源。建议先全局搜索 `@Type(` 和 `std.meta.`,再逐个迁移。 ### 小整数类型现在可以安全地隐式转换为浮点 -如果某个整数类型的所有可能值,都能被目标浮点类型精确表示,那么现在可以直接发生隐式 coercion。 +如果某个整数类型的所有可能值都能放进目标浮点类型而不发生舍入,那么这个整数可以在不显式转换的情况下 coercion 到该浮点类型。这个判断通过比较整数类型的精度位数和浮点类型的 significand 位数完成。更大的整数类型仍然需要 `@floatFromInt`。 旧写法: ```zig var foo_int: u24 = 123; var foo_float: f32 = @floatFromInt(foo_int); + +var bar_int: u25 = 123; +var bar_float: f32 = @floatFromInt(bar_int); ``` 新写法: ```zig var foo_int: u24 = 123; -var foo_float: f32 = foo_int; +var foo_float: f32 = foo_int; // 安全 coercion + +var bar_int: u25 = 123; +var bar_float: f32 = @floatFromInt(bar_int); // 仍然需要显式转换 ``` -注意这只适用于“不会丢精度”的情况。像 `u25 -> f32` 这种仍然需要显式写 `@floatFromInt`。 +这是“改善 Zig 游戏开发人体工学”这项更大工作的组成部分。 ### 运行时向量索引被禁止 @@ -386,6 +455,8 @@ for (&array) |elem| { 如果你确实需要逐项遍历向量,请先把它显式 coercion 成数组,再做索引或遍历。 +这项变化是 `Reworked Byval Syntax Lowering` 的一部分。 + ### 数组与向量不再支持旧式内存强转 `0.16.0` 不再鼓励通过 `@ptrCast` 在数组内存和向量内存之间来回转换。如果你之前是在做同构数据的值级转换,请直接使用 coercion: @@ -446,13 +517,13 @@ fn bar() noreturn { 后续官方计划继续以同样的思路加入更多类似的编译错误。 -### `packed` 与 `extern` 规则更严格 +### `packed union` 中不再允许 unused bits -#### `packed union` 需要明确 backing integer,并保证各字段 bit size 一致 +此前 `packed union` 的表示到 bit 的映射并不总是只有一种明显方式,而这种唯一性是其他 packed 类型想要具备的属性。例如,`enum(u5) { ... }` 明确表示 5 个 bit,方式也显然,因此允许出现在 packed 上下文中;但 `?u8` 有两种合理方式映射到 9 个 bit,因此不允许出现在 packed 上下文中。 -以前 Zig 对 `packed union` 的位级布局有一些隐式推断。`0.16.0` 开始要求它更明确。 +现在通过要求 `packed union` 的所有字段都具有与 backing integer 类型相同的 `@bitSizeOf` 来消除这种歧义。 -旧写法: +升级指南: ```zig const U = packed union { @@ -461,7 +532,7 @@ const U = packed union { }; ``` -新写法: +⬇️ ```zig const U = packed union(u16) { @@ -473,21 +544,36 @@ const U = packed union(u16) { }; ``` -总结一下这条规则:如果你需要 `packed union`,就请明确写出 backing integer,并保证每个字段都能映射到同样大小的位表示。 +### `packed struct` / `packed union` 不再允许指针字段 + +`packed struct` 和 `packed union` 类型的字段不再允许是指针。这实现了 proposal [#24657](https://github.com/ziglang/zig/issues/24657)。 + +这项变化的主要原因是:包含非字节对齐指针的常量值无法在绝大多数二进制格式中表示。另外,一些目标平台上的指针不能仅用地址位表示,还包含额外 metadata bit;在这种情况下,把指针打包进整数没有意义,而 `packed` 类型承诺的正是这种整数式位级表示。 + +如果你依赖了 `packed` 类型中的指针字段,可以改用 `usize` 字段,并通过 `@ptrFromInt` 和 `@intFromPtr` 在指针与整数之间转换: + +```zig +const addr: usize = @intFromPtr(ptr); +const ptr_again: *T = @ptrFromInt(addr); +``` + +### `packed union` 允许显式 backing integer + +旧版本 Zig 已经允许 `packed struct` 类型用 `packed struct(T)` 语法指定 backing integer 类型,但不允许 `packed union` 这么做。Zig `0.16.0` 现在允许了。 -之前 `packed struct(T)` 已经可以指定 backing integer,但 `packed union(T)` 不行;`0.16.0` 把这条限制也去掉了。下面是用普通声明语法和用 `@Union` builtin 同时构造同一个 packed union 的例子: +`packed_union_explicit_backing_int.zig` ```zig -// 用普通声明语法 +// 常规声明 packed union 类型 const Split16 = packed union(u16) { raw: MaybeSigned16, split: packed struct { low: u8, high: u8 }, }; -// 用 `@Union` builtin +// 使用 `@Union` 构造 packed union 类型 const MaybeSigned16 = @Union( .@"packed", - u16, // backing integer + u16, // backing integer type &.{ "unsigned", "signed" }, &.{ u16, i16 }, &@splat(.{}), @@ -499,70 +585,149 @@ test "use packed union type with explicit backing integer" { try testing.expectEqual(0xFE, u.split.low); try testing.expectEqual(0xFF, u.split.high); } + +const testing = @import("std").testing; ``` -由于下面 “`extern` 场景必须显式指定 tag type / backing type” 这条规则的存在,现在某些场景下显式指定 packed union 的 backing integer 是必须的。 +Shell: -#### `packed struct` / `packed union` 不再允许指针字段 +```sh +$ zig test packed_union_explicit_backing_int.zig +1/1 packed_union_explicit_backing_int.test.use packed union type with explicit backing integer...OK +All 1 tests passed. +``` -如果你过去把指针直接塞进 `packed` 类型里,现在需要改成整数保存地址: +注意,由于下面 “`extern` 场景必须显式指定 tag type / backing type” 这条规则的存在,现在某些场景下显式指定 packed union 的 backing integer 是必须的。 -```zig -const addr: usize = @intFromPtr(ptr); -const ptr_again: *T = @ptrFromInt(addr); -``` +### `extern` 场景下必须显式指定 tag type / backing type -这项变更的核心原因是:很多目标平台里,指针并不只是“裸地址位”,而 `packed` 类型又承诺了精确的位级布局,因此两者不再兼容。 +具有推断整数 tag type 的 `enum` 类型,以及具有推断整数 backing type 的 `packed struct` 和 `packed union` 类型,不再被视为合法的 `extern` 类型。这实现了 proposal [#24714](https://github.com/ziglang/zig/issues/24714)。 -#### `extern` 场景下必须显式指定 tag type / backing type +这项 breaking change 是为了避免一个类型的 ABI 完全由字段隐式决定。尤其是因为 `u8` 和 `i8` 在某些上下文中可能有不同 ABI;如果选择是隐式的,就不清楚到底使用哪一个。 -`enum`、`packed struct`、`packed union` 只要被用于 `extern` / `export` 场景,就不能再依赖隐式推断的底层整数类型。 +如果这在你的代码中引入了编译错误,请添加显式 tag type 或 backing type 来解决。(另见上面 “`packed union` 允许显式 backing integer” 这项 Zig `0.16.0` 相关语言变化。) -旧写法: +`extern_implicit_backing_type.zig` ```zig const Enum = enum { a, b, c, d }; const PackedStruct = packed struct { a: u4, b: u4 }; const PackedUnion = packed union { a: u8, b: i8 }; + +export var some_enum: Enum = .a; +export var some_packed_struct: PackedStruct = .{ .a = 1, .b = 2 }; +export var some_packed_union: PackedUnion = .{ .a = 123 }; ``` -新写法: +Shell: + +```sh +$ zig test extern_implicit_backing_type.zig +/home/ci/.cache/act/0d10aab40ec56bb3/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:5:1: error: unable to export type 'extern_implicit_backing_type.Enum' +export var some_enum: Enum = .a; +^~~~~~ +/home/ci/.cache/act/0d10aab40ec56bb3/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:1:14: note: integer tag type of enum is inferred +const Enum = enum { a, b, c, d }; + ^~~~~~~~~~~~~~~~~~~ +/home/ci/.cache/act/0d10aab40ec56bb3/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:1:14: note: consider explicitly specifying the integer tag type +/home/ci/.cache/act/0d10aab40ec56bb3/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:1:14: note: enum declared here +/home/ci/.cache/act/0d10aab40ec56bb3/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:6:1: error: unable to export type 'extern_implicit_backing_type.PackedStruct' +export var some_packed_struct: PackedStruct = .{ .a = 1, .b = 2 }; +^~~~~~ +/home/ci/.cache/act/0d10aab40ec56bb3/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:6:1: note: inferred backing integer of packed struct has unspecified signedness +/home/ci/.cache/act/0d10aab40ec56bb3/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:2:29: note: struct declared here +const PackedStruct = packed struct { a: u4, b: u4 }; + ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~ +/home/ci/.cache/act/0d10aab40ec56bb3/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:7:1: error: unable to export type 'extern_implicit_backing_type.PackedUnion' +export var some_packed_union: PackedUnion = .{ .a = 123 }; +^~~~~~ +/home/ci/.cache/act/0d10aab40ec56bb3/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:7:1: note: inferred backing integer of packed union has unspecified signedness +/home/ci/.cache/act/0d10aab40ec56bb3/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:3:28: note: union declared here +const PackedUnion = packed union { a: u8, b: i8 }; + ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~ +``` + +⬇️ `extern_explicit_backing_type.zig` ```zig const Enum = enum(u8) { a, b, c, d }; const PackedStruct = packed struct(u8) { a: u4, b: u4 }; const PackedUnion = packed union(u8) { a: u8, b: i8 }; + +export var some_enum: Enum = .a; +export var some_packed_struct: PackedStruct = .{ .a = 1, .b = 2 }; +export var some_packed_union: PackedUnion = .{ .a = 123 }; ``` -如果你的类型需要跨 ABI 边界导出,请把 tag type 或 backing type 明确写出来,不要再依赖编译器推断。 +Shell: + +```sh +$ zig test extern_explicit_backing_type.zig +All 0 tests passed. +``` ### 浮点取整内建现在可以直接产出整数 -`@floor`、`@ceil`、`@round`、`@trunc` 现在可以直接把浮点值转成整数值。 +`@floor`、`@ceil`、`@round` 和 `@trunc` 现在可以用于把浮点值转换成整数值: -旧写法: +`float-conversion.zig` ```zig -const x: i32 = @intFromFloat(@round(value)); +const std = @import("std"); +const expectEqual = std.testing.expectEqual; + +test "round to int" { + try example(12, 12.34); + try example(13, 12.50); +} + +fn example(expected: u8, value: f32) !void { + const actual: u8 = @round(value); + try expectEqual(expected, actual); +} ``` -新写法: +Shell: -```zig -const x: i32 = @round(value); +```sh +$ zig test float-conversion.zig +1/1 float-conversion.test.round to int...OK +All 1 tests passed. ``` -这项改动本身不一定会让旧代码报错,但会让很多“先取整、再转整数”的写法明显简化。 +`@intFromFloat` 现在与 `@trunc` 重复,因此已被 deprecated。 + +这是“改善 Zig 游戏开发人体工学”这项更大工作的组成部分。 ### 一元浮点内建会向下转发结果类型 -`@sqrt`、`@sin`、`@cos`、`@tan`、`@exp`、`@exp2`、`@log`、`@log2`、`@log10`、`@floor`、`@ceil`、`@trunc`、`@round` 现在都会把外层的结果类型向内转发,于是下面这种过去无法直接写的式子在 `0.16.0` 是合法的: +过去 Zig 不会通过下面这些 builtin 函数继续转发结果类型: + +```zig +@sqrt +@sin +@cos +@tan +@exp +@exp2 +@log +@log2 +@log10 +@floor +@ceil +@trunc +@round +``` + +现在这一点已经改变。过去你不能写: ```zig const x: f64 = @sqrt(@floatFromInt(N)); ``` -之前 `@sqrt` 不会把 `f64` 这个结果类型传给内层的 `@floatFromInt`,所以你必须手工加一层中间变量。这项改动不会让旧代码报错,但能消除大量样板。 +因为 `@sqrt` 不会把 `f64` 这个结果类型传给 `@floatFromInt`;现在可以了。 + +这是“改善 Zig 游戏开发人体工学”这项更大工作的组成部分。 ### `*u8` 与 `*align(1) u8` 不再是同一个类型 @@ -572,17 +737,55 @@ const x: f64 = @sqrt(@floatFromInt(N)); 只有在你显式比较 `@TypeOf(...)` 的相等性、或者依赖 `@typeInfo` 反射时,才需要顺手处理一下。 +这项变化是 `Reworked Type Resolution` 的一部分。 + +### 依赖环规则被简化 + +现在有一些新场景会被视为 dependency loop,而旧版本里不会。 + +不过,由于类型检查规则被简化、编译错误信息被增强,现在 dependency loop 为什么存在会更明显。这也降低了正式描述 Zig 语言规范的难度。 + +这项变化是 `Reworked Type Resolution` 的一部分。 + ### Zero-bit tuple 字段不再被隐式标记为 `comptime` -`0.14` 时引入的“tuple 中 zero-bit 字段自动变成 `comptime` 字段”这个隐式规则在 `0.16.0` 被回滚。也就是说: +在 `0.14.0` 时,一个无意引入的规则会把 zero-bit 类型的 tuple 字段隐式提升为 `comptime` 字段: ```zig -const S = struct { void }; -@typeInfo(S).@"struct".fields[0].is_comptime -// 0.15.x 下为 true,0.16.0 下为 false +comptime { + const S = struct { void }; + @compileLog(@typeInfo(S).@"struct".fields[0].is_comptime); // @as(bool, true) +} ``` -这个改动几乎不会影响任何真实代码,因为 zero-bit 字段的值依然是 comptime-known 的。但如果你直接读 `std.builtin.StructField.is_comptime`,或者依赖“带 / 不带 `comptime` 的 tuple 互为同一类型”的写法,就需要相应调整。 +Zig `0.16.0` 回滚了这个变化:上面的 tuple 字段不再被视为 `comptime` 字段。不过,这**不会**阻止该字段的值始终是 comptime-known: + +```zig +test "zero-bit tuple field is comptime-known" { + const S = struct { u32, void }; + var runtime_known: S = undefined; + runtime_known = .{ 123, {} }; + // 即便 tuple 是 runtime-known,zero-bit 字段仍然是 comptime-known: + comptime assert(runtime_known[1] == {}); +} +const assert = @import("std").debug.assert; +``` + +换句话说,这项变化几乎完全不是 breaking。唯一可能影响旧代码的情况是:你直接依赖 `@typeInfo` 中的 `std.builtin.StructField.is_comptime`,或者依赖“带显式 `comptime` 字段的 tuple 与不带显式 `comptime` 字段的 tuple 互相等价”: + +```zig +//! 这两个测试在 Zig 0.15.x 中都会通过,但在 Zig 0.16.x 中会失败。 +test "zero-bit tuple field is comptime" { + const S = struct { void }; + try expect(@typeInfo(S).@"struct".fields[0].is_comptime); +} +test "comptime annotation on zero-bit field is irrelevant to type equivalence" { + const A = struct { void }; + const B = struct { comptime void = {} }; + try expect(A == B); +} +const expect = @import("std").testing.expect; +``` ### 字段分析变成 lazy @@ -838,7 +1041,7 @@ const err = std.process.replace(io, .{ .argv = argv }); ### `std.Thread.Pool` 被移除 -`std.Thread.Pool` 已经从标准库中移除。最常见的迁移方向,是改用 `std.Io.async` 或 `std.Io.Group.async`。 +`std.Thread.Pool` 已经从标准库中移除。官方迁移建议更谨慎:如果旧代码只是用 `spawnWg` 这类“启动一组任务,然后等待 group 完成”的简单模式,可以迁到 `std.Io.async` / `std.Io.Group.async`;如果依赖复杂同步、必须保证任务真正并发执行才正确,或需要控制并发度,应参考 `std.Io.concurrent` 相关文档,而不是直接机械替换。 如果你过去用的是“提交一组任务,然后等待全部结束”的模式,通常可以这样迁移: @@ -1066,62 +1269,62 @@ file.close(io); 重命名 / 迁移过的 API(节选最常用的部分): -| 0.15.x | 0.16.0 | -| --- | --- | -| `fs.Dir` | `std.Io.Dir` | -| `fs.File` | `std.Io.File` | -| `fs.cwd` | `std.Io.Dir.cwd` | -| `fs.copyFileAbsolute` | `std.Io.Dir.copyFileAbsolute` | -| `fs.makeDirAbsolute` | `std.Io.Dir.createDirAbsolute` | -| `fs.deleteDirAbsolute` | `std.Io.Dir.deleteDirAbsolute` | -| `fs.openDirAbsolute` | `std.Io.Dir.openDirAbsolute` | -| `fs.openFileAbsolute` | `std.Io.Dir.openFileAbsolute` | -| `fs.accessAbsolute` | `std.Io.Dir.accessAbsolute` | -| `fs.createFileAbsolute` | `std.Io.Dir.createFileAbsolute` | -| `fs.deleteFileAbsolute` | `std.Io.Dir.deleteFileAbsolute` | -| `fs.renameAbsolute` | `std.Io.Dir.renameAbsolute` | -| `fs.readLinkAbsolute` | `std.Io.Dir.readLinkAbsolute` | -| `fs.symLinkAbsolute` | `std.Io.Dir.symLinkAbsolute` | -| `fs.realpath` | `std.Io.Dir.realPathFileAbsolute` | -| `fs.realpathAlloc` | `std.Io.Dir.realPathFileAbsoluteAlloc` | -| `fs.rename` | `std.Io.Dir.rename` | -| `fs.has_executable_bit` | `std.Io.File.Permissions.has_executable_bit` | -| `fs.defaultWasiCwd` | `std.os.defaultWasiCwd` | -| `fs.openSelfExe` | `std.process.openExecutable` | -| `fs.selfExePath` | `std.process.executablePath` | -| `fs.selfExePathAlloc` | `std.process.executablePathAlloc` | -| `fs.selfExeDirPath` | `std.process.executableDirPath` | -| `fs.selfExeDirPathAlloc` | `std.process.executableDirPathAlloc` | -| `fs.Dir.setAsCwd` | `std.process.setCurrentDir` | -| `fs.Dir.realpath` | `std.Io.Dir.realPathFile` | -| `fs.Dir.realpathAlloc` | `std.Io.Dir.realPathFileAlloc` | -| `fs.Dir.makeDir` | `std.Io.Dir.createDir` | -| `fs.Dir.makePath` | `std.Io.Dir.createDirPath` | -| `fs.Dir.makeOpenDir` | `std.Io.Dir.createDirPathOpen` | -| `fs.Dir.atomicSymLink` | `std.Io.Dir.symLinkAtomic` | -| `fs.Dir.chmod` | `std.Io.Dir.setPermissions` | -| `fs.Dir.chown` | `std.Io.Dir.setOwner` | -| `fs.File.Mode` | `std.Io.File.Permissions` | -| `fs.File.PermissionsWindows` | `std.Io.File.Permissions` | -| `fs.File.PermissionsUnix` | `std.Io.File.Permissions` | -| `fs.File.default_mode` | `std.Io.File.Permissions.default_file` | -| `fs.File.getOrEnableAnsiEscapeSupport` | `std.Io.File.enableAnsiEscapeCodes` | -| `fs.File.setEndPos` | `std.Io.File.setLength` | -| `fs.File.getEndPos` | `std.Io.File.length` | +| 0.15.x | 0.16.0 | +| ------------------------------------------- | --------------------------------------------------------------- | +| `fs.Dir` | `std.Io.Dir` | +| `fs.File` | `std.Io.File` | +| `fs.cwd` | `std.Io.Dir.cwd` | +| `fs.copyFileAbsolute` | `std.Io.Dir.copyFileAbsolute` | +| `fs.makeDirAbsolute` | `std.Io.Dir.createDirAbsolute` | +| `fs.deleteDirAbsolute` | `std.Io.Dir.deleteDirAbsolute` | +| `fs.openDirAbsolute` | `std.Io.Dir.openDirAbsolute` | +| `fs.openFileAbsolute` | `std.Io.Dir.openFileAbsolute` | +| `fs.accessAbsolute` | `std.Io.Dir.accessAbsolute` | +| `fs.createFileAbsolute` | `std.Io.Dir.createFileAbsolute` | +| `fs.deleteFileAbsolute` | `std.Io.Dir.deleteFileAbsolute` | +| `fs.renameAbsolute` | `std.Io.Dir.renameAbsolute` | +| `fs.readLinkAbsolute` | `std.Io.Dir.readLinkAbsolute` | +| `fs.symLinkAbsolute` | `std.Io.Dir.symLinkAbsolute` | +| `fs.realpath` | `std.Io.Dir.realPathFileAbsolute` | +| `fs.realpathAlloc` | `std.Io.Dir.realPathFileAbsoluteAlloc` | +| `fs.rename` | `std.Io.Dir.rename` | +| `fs.has_executable_bit` | `std.Io.File.Permissions.has_executable_bit` | +| `fs.defaultWasiCwd` | `std.os.defaultWasiCwd` | +| `fs.openSelfExe` | `std.process.openExecutable` | +| `fs.selfExePath` | `std.process.executablePath` | +| `fs.selfExePathAlloc` | `std.process.executablePathAlloc` | +| `fs.selfExeDirPath` | `std.process.executableDirPath` | +| `fs.selfExeDirPathAlloc` | `std.process.executableDirPathAlloc` | +| `fs.Dir.setAsCwd` | `std.process.setCurrentDir` | +| `fs.Dir.realpath` | `std.Io.Dir.realPathFile` | +| `fs.Dir.realpathAlloc` | `std.Io.Dir.realPathFileAlloc` | +| `fs.Dir.makeDir` | `std.Io.Dir.createDir` | +| `fs.Dir.makePath` | `std.Io.Dir.createDirPath` | +| `fs.Dir.makeOpenDir` | `std.Io.Dir.createDirPathOpen` | +| `fs.Dir.atomicSymLink` | `std.Io.Dir.symLinkAtomic` | +| `fs.Dir.chmod` | `std.Io.Dir.setPermissions` | +| `fs.Dir.chown` | `std.Io.Dir.setOwner` | +| `fs.File.Mode` | `std.Io.File.Permissions` | +| `fs.File.PermissionsWindows` | `std.Io.File.Permissions` | +| `fs.File.PermissionsUnix` | `std.Io.File.Permissions` | +| `fs.File.default_mode` | `std.Io.File.Permissions.default_file` | +| `fs.File.getOrEnableAnsiEscapeSupport` | `std.Io.File.enableAnsiEscapeCodes` | +| `fs.File.setEndPos` | `std.Io.File.setLength` | +| `fs.File.getEndPos` | `std.Io.File.length` | | `fs.File.seekTo` / `seekBy` / `seekFromEnd` | `std.Io.File.Reader.seekTo` / `Reader.seekBy` / `Writer.seekTo` | -| `fs.File.getPos` | `std.Io.File.Reader.logicalPos` / `std.Io.Writer.logicalPos` | -| `fs.File.mode` | `std.Io.File.stat().permissions.toMode` | -| `fs.File.chmod` | `std.Io.File.setPermissions` | -| `fs.File.chown` | `std.Io.File.setOwner` | -| `fs.File.updateTimes` | `std.Io.File.setTimestamps` / `setTimestampsNow` | -| `fs.File.read` / `readv` | `std.Io.File.readStreaming` | -| `fs.File.pread` / `preadv` | `std.Io.File.readPositional` | -| `fs.File.preadAll` | `std.Io.File.readPositionalAll` | -| `fs.File.write` / `writev` | `std.Io.File.writeStreaming` | -| `fs.File.pwrite` / `pwritev` | `std.Io.File.writePositional` | -| `fs.File.writeAll` | `std.Io.File.writeStreamingAll` | -| `fs.File.pwriteAll` | `std.Io.File.writePositionalAll` | -| `fs.File.copyRange` / `copyRangeAll` | `std.Io.File.writer` | +| `fs.File.getPos` | `std.Io.File.Reader.logicalPos` / `std.Io.Writer.logicalPos` | +| `fs.File.mode` | `std.Io.File.stat().permissions.toMode` | +| `fs.File.chmod` | `std.Io.File.setPermissions` | +| `fs.File.chown` | `std.Io.File.setOwner` | +| `fs.File.updateTimes` | `std.Io.File.setTimestamps` / `setTimestampsNow` | +| `fs.File.read` / `readv` | `std.Io.File.readStreaming` | +| `fs.File.pread` / `preadv` | `std.Io.File.readPositional` | +| `fs.File.preadAll` | `std.Io.File.readPositionalAll` | +| `fs.File.write` / `writev` | `std.Io.File.writeStreaming` | +| `fs.File.pwrite` / `pwritev` | `std.Io.File.writePositional` | +| `fs.File.writeAll` | `std.Io.File.writeStreamingAll` | +| `fs.File.pwriteAll` | `std.Io.File.writePositionalAll` | +| `fs.File.copyRange` / `copyRangeAll` | `std.Io.File.writer` | 这一表里许多函数除了改名,还顺手在签名里塞了一个 `io: std.Io` 参数。 From 9b0b1ca325db873764c93cafead9313eeb54d78c Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 21 Jun 2026 21:39:24 +0800 Subject: [PATCH 6/9] =?UTF-8?q?docs:=20=E7=BB=9F=E4=B8=80=200.16.0=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E4=B8=AD=E7=9A=84=E4=B8=AD=E6=96=87=E6=A0=87?= =?UTF-8?q?=E7=82=B9=E6=8E=92=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 统一中英文混排中的引号与连接符空格,减少阅读视觉噪音, 避免同一课程内出现多套写法,提升术语与示例格式一致性。 --- course/environment/zig-command.md | 2 +- course/update/0.16.0-description.md | 8 ++++---- course/update/upgrade-0.16.0.md | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/course/environment/zig-command.md b/course/environment/zig-command.md index e62567f5..7e3de498 100644 --- a/course/environment/zig-command.md +++ b/course/environment/zig-command.md @@ -84,7 +84,7 @@ outline: deep ```sh # 仅获取包的哈希值 $ zig fetch https://github.com/webui-dev/zig-webui/archive/main.tar.gz -# 输出形如:包名-版本-哈希(以实际命令为准) +# 输出形如:包名 - 版本 - 哈希(以实际命令为准) ``` 如果你希望将包直接添加为依赖项,可以附加 `--save` 参数: diff --git a/course/update/0.16.0-description.md b/course/update/0.16.0-description.md index 68dd28c6..4cb6888d 100644 --- a/course/update/0.16.0-description.md +++ b/course/update/0.16.0-description.md @@ -352,7 +352,7 @@ pub fn main(init: std.process.Init) !void { - `std.process.getCwd*` 改名为 `currentPath*` - `fs.path.relative` 变成纯函数,需要显式传入上下文 - `File.Stat.atime` 变成可选值 -- `std.mem` 里 “index of” 系列统一更名为 “find” +- `std.mem` 里“index of”系列统一更名为“find” - 一批容器继续向 unmanaged 方向迁移,`PriorityQueue` / `PriorityDequeue` 的命名也更统一了 这些调整单看都不算大新闻,但合在一起,就是一次很典型的 Zig 式“去历史包袱”整理。 @@ -512,7 +512,7 @@ Windows 也是 `0.16.0` 里非常有意思的一条线,前面几节已经覆 LLVM 后端这一轮的进展有几条: -- **实验性支持增量编译**——这并不会加速 “LLVM Emit Object” 这一步(LLVM 自己负责的部分我们做不了什么),但加速了 Zig 编译器侧生成 LLVM bitcode 的过程,因此当你的代码本身就有编译错误时,你能在 LLVM 后端下也获得近乎瞬时的反馈 +- **实验性支持增量编译**——这并不会加速“LLVM Emit Object”这一步(LLVM 自己负责的部分我们做不了什么),但加速了 Zig 编译器侧生成 LLVM bitcode 的过程,因此当你的代码本身就有编译错误时,你能在 LLVM 后端下也获得近乎瞬时的反馈 - LLVM bitcode 体积下降 3-7% - 在某些情况下编译速度略提升约 3% - 修掉了零字节 payload union 的调试信息 @@ -521,7 +521,7 @@ LLVM 后端这一轮的进展有几条: LLVM 后端目前通过了 2004 / 2010(100%)行为测试。Matthew 还实验过把 tagged union 和 error union 用 DWARF 的 variant 类型表达,让调试器只显示“当前激活的字段”——但 LLDB 对 variant 类型的支持只有在语言被标成 Rust 时才启用,因此暂未落地。这条路径未来下游若改善还可能再走。 -后续还会继续推进 LLVM 后端的并行化:让多个线程同时为不同函数生成 LLVM IR,再由一个 “linker” 线程合并。 +后续还会继续推进 LLVM 后端的并行化:让多个线程同时为不同函数生成 LLVM IR,再由一个“linker”线程合并。 ### 重做 byval 语法降级 @@ -648,7 +648,7 @@ Fuzz 测试接口是 `0.16.0` 里另一个会直接影响用户代码的 breakin ### 配套的 AST Smith 已经替 zig fmt 找到 20 个 bug -新的 Smith 接口本身已经被用来构建一个 “AST Smith”——专门生成随机但合法的 AST。把它丢给 `zig fmt`(再加上一些更早期的简单随机源码测试),一共发现并修复了 **20 个独立 bug**,其中一部分是新发现的。 +新的 Smith 接口本身已经被用来构建一个“AST Smith”——专门生成随机但合法的 AST。把它丢给 `zig fmt`(再加上一些更早期的简单随机源码测试),一共发现并修复了 **20 个独立 bug**,其中一部分是新发现的。 它还顺手修了几个“PEG 与 parser 不一致”的问题,例如以前 tuple 不能包含以 `extern` 或 `inline` 起头的类型——`const T = struct { u64, extern struct { a: u64 }, u32 }` 在以前会直接报错。 diff --git a/course/update/upgrade-0.16.0.md b/course/update/upgrade-0.16.0.md index fca691c3..5fb6e661 100644 --- a/course/update/upgrade-0.16.0.md +++ b/course/update/upgrade-0.16.0.md @@ -35,7 +35,7 @@ switch (u) { bug 修复方面: -- 大量 “one-possible-value” 类型上的 switch 相关 bug 被修复 +- 大量“one-possible-value”类型上的 switch 相关 bug 被修复 - 在 error 上做 switch 时,关于 unreachable `else` prong 的规则现在适用于**所有**对 error 的 switch,而不仅是 `switch_block_err_union`,且基于 AST 正确判断 - 对 `void` 做 switch 时,不再无条件要求 `else` prong - lazy values 在与 prong item 比较前会被正确求值 @@ -274,13 +274,13 @@ std.meta.Tuple(&.{ u32, [2]f64 }) ) ``` -这是几个新 builtin 中用 “struct of arrays” 风格接收参数的代表。这种风格的好处是“给所有元素一个统一默认值”非常容易——比如想给所有参数用默认属性 `.{}`,用 `&@splat(.{})` 即可: +这是几个新 builtin 中用“struct of arrays”风格接收参数的代表。这种风格的好处是“给所有元素一个统一默认值”非常容易——比如想给所有参数用默认属性 `.{}`,用 `&@splat(.{})` 即可: ```zig @Fn(param_types, &@splat(.{}), ReturnType, .{ .@"callconv" = .c }) ``` -`@Struct` 同样采用 “struct of arrays”:字段名、字段类型、字段属性各自传一组数组,属性里包含 alignment、`comptime` 标志、字段默认值: +`@Struct` 同样采用“struct of arrays”:字段名、字段类型、字段属性各自传一组数组,属性里包含 alignment、`comptime` 标志、字段默认值: ```zig @Type(.{ .@"struct" = .{ @@ -597,7 +597,7 @@ $ zig test packed_union_explicit_backing_int.zig All 1 tests passed. ``` -注意,由于下面 “`extern` 场景必须显式指定 tag type / backing type” 这条规则的存在,现在某些场景下显式指定 packed union 的 backing integer 是必须的。 +注意,由于下面“`extern` 场景必须显式指定 tag type / backing type”这条规则的存在,现在某些场景下显式指定 packed union 的 backing integer 是必须的。 ### `extern` 场景下必须显式指定 tag type / backing type @@ -605,7 +605,7 @@ All 1 tests passed. 这项 breaking change 是为了避免一个类型的 ABI 完全由字段隐式决定。尤其是因为 `u8` 和 `i8` 在某些上下文中可能有不同 ABI;如果选择是隐式的,就不清楚到底使用哪一个。 -如果这在你的代码中引入了编译错误,请添加显式 tag type 或 backing type 来解决。(另见上面 “`packed union` 允许显式 backing integer” 这项 Zig `0.16.0` 相关语言变化。) +如果这在你的代码中引入了编译错误,请添加显式 tag type 或 backing type 来解决。(另见上面“`packed union` 允许显式 backing integer”这项 Zig `0.16.0` 相关语言变化。) `extern_implicit_backing_type.zig` @@ -1229,7 +1229,7 @@ var writer: std.Io.Writer = .fixed(buffer); ### 文件系统和路径 API 有一批实用迁移点 -`fs` 全部 API 都迁到了 `Io`。和 0.15 那次 “writergate” 不同,这次虽然 breaking 范围很大,但绝大多数迁移机械、不需要特别多的判断。最典型的形态就是给原本无参的方法加一个 `io`: +`fs` 全部 API 都迁到了 `Io`。和 0.15 那次“writergate”不同,这次虽然 breaking 范围很大,但绝大多数迁移机械、不需要特别多的判断。最典型的形态就是给原本无参的方法加一个 `io`: ```zig file.close(); @@ -1578,7 +1578,7 @@ try atomic_file.replace(io); // 或者把上面的 .replace 改为 false,再 也就是说,Zig 不再想长期维护那批半高层、半底层的历史包装函数。 -### `std.mem` 的 “index of” 系列统一更名为 “find” +### `std.mem` 的“index of”系列统一更名为“find” `std.mem` 现在统一使用 `find` 作为“查找子串位置”的概念名称,并新增了 `cut`、`cutPrefix`、`cutSuffix`、`cutScalar`、`cutLast`、`cutLastScalar` 等函数。 From 2ee0f588b747892f7b2ae739c7021491ed196f6a Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 21 Jun 2026 22:10:12 +0800 Subject: [PATCH 7/9] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=200.16=20Messag?= =?UTF-8?q?ePack=20=E7=A4=BA=E4=BE=8B=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- course/code/16/package_management_importer/build.zig.zon | 4 ++-- course/code/release/package_management_importer/build.zig.zon | 4 ++-- course/update/0.16.0-description.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/course/code/16/package_management_importer/build.zig.zon b/course/code/16/package_management_importer/build.zig.zon index f74a0521..bedb54b3 100644 --- a/course/code/16/package_management_importer/build.zig.zon +++ b/course/code/16/package_management_importer/build.zig.zon @@ -9,8 +9,8 @@ .dependencies = .{ // 包依赖项的名字 .@"tarball-exporter" = .{ - .url = "https://github.com/zigcc/zig-msgpack/archive/33771261cc6bba98cee380392f6e95fbca30d956.tar.gz", - .hash = "zig_msgpack-0.0.8-evvueB_ZAQBNRm7kdh1FslBxMvpu5WKvU2RrYhUY_Dne", + .url = "https://github.com/zigcc/zig-msgpack/archive/refs/tags/0.0.17.tar.gz", + .hash = "zig_msgpack-0.0.14-evvueL5SBQACmim6j6klQ9wWIIG_UxGlPvVYdiNy0KT8", }, .@"path-exporter" = .{ // path 为本地包的路径 diff --git a/course/code/release/package_management_importer/build.zig.zon b/course/code/release/package_management_importer/build.zig.zon index f74a0521..bedb54b3 100644 --- a/course/code/release/package_management_importer/build.zig.zon +++ b/course/code/release/package_management_importer/build.zig.zon @@ -9,8 +9,8 @@ .dependencies = .{ // 包依赖项的名字 .@"tarball-exporter" = .{ - .url = "https://github.com/zigcc/zig-msgpack/archive/33771261cc6bba98cee380392f6e95fbca30d956.tar.gz", - .hash = "zig_msgpack-0.0.8-evvueB_ZAQBNRm7kdh1FslBxMvpu5WKvU2RrYhUY_Dne", + .url = "https://github.com/zigcc/zig-msgpack/archive/refs/tags/0.0.17.tar.gz", + .hash = "zig_msgpack-0.0.14-evvueL5SBQACmim6j6klQ9wWIIG_UxGlPvVYdiNy0KT8", }, .@"path-exporter" = .{ // path 为本地包的路径 diff --git a/course/update/0.16.0-description.md b/course/update/0.16.0-description.md index 4cb6888d..db4177a5 100644 --- a/course/update/0.16.0-description.md +++ b/course/update/0.16.0-description.md @@ -506,7 +506,7 @@ Windows 也是 `0.16.0` 里非常有意思的一条线,前面几节已经覆 对普通用户来说,这件事大体上是非破坏性的——但也确实是“一个 C 编译器替换成另一个”,所以如果你升级后发现某个 C 头文件翻译结果不一致,请按 bug 反馈。 -实现是“懒加载”的:第一次遇到 `@cImport` 时再从源码构建。 +旧 `@cImport` 路径的实现仍然是“懒加载”的:第一次遇到 `@cImport` 时再从源码构建;新的推荐路径是提前在 `build.zig` 里用 `addTranslateC` 生成模块。 ### LLVM 后端 From d4946bf89efcff0d428615f7aa577e89ee1ec966 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 21 Jun 2026 22:10:48 +0800 Subject: [PATCH 8/9] =?UTF-8?q?chore:=20=E5=BF=BD=E7=95=A5=20Zig=20?= =?UTF-8?q?=E5=8C=85=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4b2c3585..8b0098aa 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ package-lock.json zig-out zig-cache .zig-cache +zig-pkg .direnv PDF .DS_Store From efe8191b491cc7557b18f82317acd56fb68a1078 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 21 Jun 2026 22:15:22 +0800 Subject: [PATCH 9/9] =?UTF-8?q?docs:=20=E4=BF=AE=E6=AD=A3=200.16=20C=20imp?= =?UTF-8?q?ort=20=E6=97=A7=E5=86=99=E6=B3=95=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- course/update/upgrade-0.16.0.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/course/update/upgrade-0.16.0.md b/course/update/upgrade-0.16.0.md index 5fb6e661..e6b30e17 100644 --- a/course/update/upgrade-0.16.0.md +++ b/course/update/upgrade-0.16.0.md @@ -59,8 +59,6 @@ pub const c = @cImport({ @cInclude("math.h"); @cInclude("stdlib.h"); }); - -const c = @import("c.zig").c; ``` 新写法: