diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a337403d..b5a46f98 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,8 +27,9 @@ jobs: matrix: # macos 固定 15:macos-latest 已迁移到 macOS 26,Zig 在其上链接不到系统 # libSystem(所有 libc 符号 undefined),0.11-0.15 全部受影响 + # 另外 0.17 要求 macOS 15.0+,macos-15 恰好满足 os: [ubuntu-latest, macos-15, windows-latest] - version: [0.11.0, 0.12.1, 0.13.0, 0.14.0, 0.15.1] + version: [0.11.0, 0.12.1, 0.13.0, 0.14.0, 0.15.1, 0.17.0] fail-fast: false runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d8dafd09..9556e1d7 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -46,3 +46,23 @@ jobs: --exclude course/code/12/assembly.zig \ --exclude course/code/14/assembly.zig \ . + # 0.17 的 zig fmt 无法解析旧版本示例中已移除的语法(如数组乘法 `**`、 + # `errdefer |err|`),因此只用它检查 0.17 相关的代码 + lint-0_17: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Setup Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.17.0 + - name: Verify formatting + run: | + zig fmt --check \ + build.zig \ + build/0.17.zig \ + course/code/17 \ + course/code/release diff --git a/AGENTS.md b/AGENTS.md index 00a16a0c..5dc1b4a3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ ## 核心目标 1. 提供全面的 Zig 编程语言中文文档 -2. 支持多个 Zig 版本(0.11 至 0.16) +2. 支持多个 Zig 版本(0.11 至 0.17) 3. 维护可运行的代码示例 4. 记录版本升级指南和破坏性变更 5. 构建高质量的中文 Zig 社区学习资源 @@ -31,8 +31,9 @@ zig-course/ │ ├── 0.12.zig # Zig 0.12 构建逻辑 │ ├── 0.13.zig # Zig 0.13 构建逻辑 │ ├── 0.14.zig # Zig 0.14 构建逻辑 -│ ├── 0.15.zig # Zig 0.15 构建逻辑(当前重点) -│ └── 0.16.zig # Zig 0.16 构建逻辑 +│ ├── 0.15.zig # Zig 0.15 构建逻辑 +│ ├── 0.16.zig # Zig 0.16 构建逻辑 +│ └── 0.17.zig # Zig 0.17 构建逻辑(当前重点) │ ├── course/ # 主要文档内容 │ ├── .vitepress/ # VitePress 配置 @@ -54,8 +55,10 @@ zig-course/ │ │ ├── 12/ # Zig 0.12 示例 │ │ ├── 13/ → ./12 # 符号链接(与 0.12 兼容) │ │ ├── 14/ # Zig 0.14 示例 -│ │ ├── 15/ # Zig 0.15 示例(当前活跃版本) -│ │ └── release/ → ./15 # 符号链接到最新稳定版 +│ │ ├── 15/ # Zig 0.15 示例 +│ │ ├── 16/ # Zig 0.16 示例 +│ │ ├── 17/ # Zig 0.17 示例(当前活跃版本) +│ │ └── release/ # 与最新稳定版(当前为 17)内容保持一致的独立目录 │ │ │ ├── picture/ # 图片资源 │ └── public/ # 静态网站资源 @@ -191,7 +194,7 @@ Zig 源文件不在 `pnpm format` 范围内,需单独运行 `zig fmt .`。 **当前符号链接**: - `course/code/13/` → `./12` (Zig 0.13 与 0.12 兼容) -- `course/code/release/` → `./15` (指向最新稳定版) +- `course/code/release/` 是独立目录(非符号链接),内容需与最新稳定版 `course/code/17/` 保持同步 **维护规则**: @@ -269,7 +272,7 @@ zig build #### 步骤 1:创建代码示例文件 -**路径**: `course/code/15/.zig`(15 为当前活跃版本) +**路径**: `course/code/17/.zig`(17 为当前活跃版本,完成后同步到 `release/`) 代码文件结构规范: @@ -356,7 +359,7 @@ outline: deep **代码引用语法**: - `<<<@/code/release/.zig#` - 引用指定锚点的代码片段 -- `release` 是符号链接,指向当前最新稳定版本(如 15) +- `release` 目录与当前最新稳定版本(如 17)内容保持一致 - 锚点名称必须与代码文件中的 `#region` 名称完全匹配 #### 步骤 3:更新导航配置 diff --git a/README.md b/README.md index dc754781..0ba846a2 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ - **基础入门**: 包括变量、类型、流程控制、错误处理等基础知识 - **高级主题**: 深入探讨 `comptime`、异步、内存管理、C 语言交互等高级特性 - **工程实践**: 涵盖构建系统、包管理、单元测试和代码风格指南 -- **版本兼容**: 提供与 Zig 0.11-0.16 版本相对应的代码示例 +- **版本兼容**: 提供与 Zig 0.11-0.17 版本相对应的代码示例 - **实战案例**: 包含 TCP 服务器等实际项目示例 ## 📁 项目结构 @@ -84,7 +84,7 @@ zig-course/ ### 环境要求 - **Node.js**: 需要 Node.js >= 24(脚本依赖原生 TypeScript 支持),包管理器使用 [pnpm](https://pnpm.io/) -- **Zig**: 支持 0.11-0.16 版本 +- **Zig**: 支持 0.11-0.17 版本 - **autocorrect**: 用于中英文排版优化(可选) ### 快速开始 diff --git a/build.zig b/build.zig index 24f43588..8c7376f6 100644 --- a/build.zig +++ b/build.zig @@ -12,6 +12,7 @@ pub fn build(b: *Build) void { 14 => @import("build/0.14.zig").build(b), 15 => @import("build/0.15.zig").build(b), 16 => @import("build/0.16.zig").build(b), + 17 => @import("build/0.17.zig").build(b), else => @compileError("unknown zig version"), } } diff --git a/build/0.17.zig b/build/0.17.zig new file mode 100644 index 00000000..ba53819c --- /dev/null +++ b/build/0.17.zig @@ -0,0 +1,129 @@ +const std = @import("std"); +const Build = std.Build; +const log = std.log.scoped(.For_0_17_0); +const version = "17"; + +const relative_path = "course/code/" ++ version; + +/// 需要链接 libc 的单文件示例,其余示例一律不链接 libc +const libc_examples = [_][]const u8{ + "interact_with_c.zig", + "memory_manager.zig", + "pointer.zig", +}; + +pub fn build(b: *Build) void { + // get target and optimize + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const io = b.graph.io; + + // 0.17 起 configure 阶段(运行 build.zig 的过程)的结果会被缓存。 + // 这里在 configure 阶段遍历了示例目录,因此需要显式声明对该目录内容的依赖, + // 这样新增、删除或重命名示例后才会重新执行 configure。 + b.dependOnDirectoryContents(b.path(relative_path)); + + // 0.17 移除了 `b.build_root` 与 `LazyPath.getPath`,改用 `b.root`(`Cache.Path`) + var dir = b.root.openDir(io, relative_path, .{ .iterate = true }) catch |err| { + log.err("open {s} path failed, err is {t}", .{ relative_path, err }); + std.process.exit(1); + }; + defer dir.close(io); + + var iterate = dir.iterate(); + + while (iterate.next(io) catch |err| { + log.err("iterate examples_path failed, err is {t}", .{err}); + std.process.exit(1); + }) |entry| { + switch (entry.kind) { + .file => { + if (!std.mem.endsWith(u8, entry.name, ".zig")) continue; + addSingleFileExample(b, entry.name, target, optimize); + }, + .directory => { + if (entry.name[0] == '.' or std.mem.eql(u8, entry.name, "zig-out")) continue; + addProjectExample(b, entry.name); + }, + else => {}, + } + } +} + +/// 单文件示例:编译为可执行文件,并运行其中的单元测试 +fn addSingleFileExample( + b: *Build, + file_name: []const u8, + target: Build.ResolvedTarget, + optimize: std.lang.Optimize, +) void { + const output_name = file_name[0 .. file_name.len - ".zig".len]; + const path = b.fmt("{s}/{s}", .{ relative_path, file_name }); + + const imports: []const Build.Module.Import = if (std.mem.eql(u8, file_name, "interact_with_c.zig")) imports: { + const c_header = b.addWriteFiles().add("interact_with_c.h", + \\#define _NO_CRT_STDIO_INLINE 1 + \\#include + ); + // `std.Build.Step.TranslateC` 在 0.17 中已被标记为 deprecated, + // 官方推荐改为依赖 translate-c 包;为了让仓库根构建保持零依赖,这里暂时沿用内置步骤。 + const translate_c = b.addTranslateC(.{ + .root_source_file = c_header, + .target = target, + .optimize = optimize, + }); + break :imports b.allocator.dupe(Build.Module.Import, &.{ + .{ .name = "c", .module = translate_c.createModule() }, + }) catch @panic("OOM"); + } else &.{}; + + const link_libc: ?bool = for (libc_examples) |name| { + if (std.mem.eql(u8, name, file_name)) break true; + } else null; + + // build exe + const exe = b.addExecutable(.{ + .name = output_name, + .root_module = b.createModule(.{ + .root_source_file = b.path(path), + .target = target, + .optimize = optimize, + .imports = imports, + .link_libc = link_libc, + }), + }); + + // add to default install + b.installArtifact(exe); + + // build test + const unit_tests = b.addTest(.{ + .name = b.fmt("{s}_test", .{output_name}), + .root_module = b.createModule(.{ + .root_source_file = b.path(path), + .target = target, + .optimize = optimize, + .imports = imports, + .link_libc = link_libc, + }), + }); + const run_unit_tests = b.addRunArtifact(unit_tests); + // 交叉编译(例如 -Dtarget=x86_64-windows)时跳过无法在宿主机运行的测试 + run_unit_tests.skip_foreign_checks = true; + + // add to default install + b.getInstallStep().dependOn(&run_unit_tests.step); +} + +/// 项目类示例:子目录中带有独立的 build.zig,在 make 阶段执行 `zig build` +/// +/// 0.17 将 configure 与 make 拆成了两个进程,configure 结果还会被缓存, +/// 因此不能再像旧版本那样在 build.zig 里直接 spawn 子进程,而要交给 Run 步骤。 +fn addProjectExample(b: *Build, dir_name: []const u8) void { + const sub_build = b.addSystemCommand(&.{ b.graph.zig_exe, "build" }); + sub_build.setName(b.fmt("zig build ({s}/{s})", .{ relative_path, dir_name })); + sub_build.setCwd(b.path(b.fmt("{s}/{s}", .{ relative_path, dir_name }))); + sub_build.stdio = .inherit; + + b.getInstallStep().dependOn(&sub_build.step); +} diff --git a/course/.vitepress/sidebar.ts b/course/.vitepress/sidebar.ts index 4fadfba0..439f69f1 100644 --- a/course/.vitepress/sidebar.ts +++ b/course/.vitepress/sidebar.ts @@ -220,6 +220,14 @@ export default [ text: "版本说明", collapsed: true, items: [ + { + text: "0.17.0 升级指南", + link: "/update/upgrade-0.17.0", + }, + { + text: "0.17.0 版本说明", + link: "/update/0.17.0-description", + }, { text: "0.16.0 升级指南", link: "/update/upgrade-0.16.0", diff --git a/course/.vitepress/theme/config.ts b/course/.vitepress/theme/config.ts index 08af1f81..b94ddfd2 100644 --- a/course/.vitepress/theme/config.ts +++ b/course/.vitepress/theme/config.ts @@ -1,3 +1,3 @@ -const version: string = "0.16.0"; +const version: string = "0.17.0"; export { version }; diff --git a/course/advanced/assembly.md b/course/advanced/assembly.md index 72aea342..d19a1436 100644 --- a/course/advanced/assembly.md +++ b/course/advanced/assembly.md @@ -51,7 +51,7 @@ outline: deep <<<@/code/release/assembly.zig#inline_assembly -上面这段示例当前没有直接执行内联汇编:旧的 `syscall` 写法保留在注释中,实际路径只调用 `std.process.exit(0)`,避免把未迁移的内联汇编语法误当作 Zig 0.16 可运行示例。 +上面这段示例当前没有直接执行内联汇编:旧的 `syscall` 写法保留在注释中,实际路径只调用 `std.process.exit(0)`,避免把未迁移的内联汇编语法误当作当前版本可运行的示例。 内联汇编是以 `asm` 关键字开头的一个表达式,这说明它可以返回值(也可以不返回值),`volatile` 关键字会通知编译器,内联汇编的表达式会被某些编译器未知的因素更改(例如操作系统,硬件 MMIO 或者其他线程等等),这样编译器就不会额外优化这段内联汇编。 diff --git a/course/advanced/interact-with-c.md b/course/advanced/interact-with-c.md index 694fc536..b59c07d8 100644 --- a/course/advanced/interact-with-c.md +++ b/course/advanced/interact-with-c.md @@ -36,15 +36,31 @@ Zig 定义了几个对应 C ABI 的基本类型: ## C Header 导入 -C 语言共享类型通常通过引入头文件实现。Zig 0.16 起推荐把头文件翻译放到 `build.zig` 中:先用 `addTranslateC` 生成模块,再在 Zig 代码里像普通模块一样 `@import("c")`。 +C 语言共享类型通常通过引入头文件实现。`@cImport` 在 Zig 0.16 中被标记为 deprecated,并已在 Zig 0.17 中**彻底移除**,因此头文件翻译必须放到 `build.zig` 中完成:先把 C 头文件翻译为一个 Zig 模块,再在 Zig 代码里像普通模块一样 `@import("c")`。 -`build.zig` 中的核心写法如下: +Zig 0.17 官方推荐使用 ZSF 维护的 [translate-c](https://codeberg.org/ziglang/translate-c) 包来完成翻译,它与构建系统内置的 `addTranslateC` 是同一套实现,但提供了更多配置项,并且拥有独立于 Zig 工具链的发布节奏。首先添加依赖: + +```sh +zig fetch --save git+https://codeberg.org/ziglang/translate-c#2.0.0 +``` + +::: warning 注意版本 + +translate-c 的 `main` 分支跟踪的是 Zig 的 master 分支(当前已是 0.18 开发版),使用 Zig 0.17 时请固定到 `2.0.0` 标签(或 `zig-0.17.x` 分支),否则可能拉取到不兼容的版本。 + +::: + +然后在 `build.zig` 中这样使用: ```zig -const translate_c = b.addTranslateC(.{ - .root_source_file = b.path("src/c.h"), +const Translator = @import("translate_c").Translator; + +const translate_c = b.dependency("translate_c", .{}); +const c: Translator = .init(translate_c, .{ + .c_source_file = b.path("src/c.h"), .target = target, .optimize = optimize, + // 默认 link_libc = true,翻译出的模块会自动链接 libc }); const exe = b.addExecutable(.{ @@ -54,11 +70,10 @@ const exe = b.addExecutable(.{ .target = target, .optimize = optimize, .imports = &.{ - .{ .name = "c", .module = translate_c.createModule() }, + .{ .name = "c", .module = c.mod }, }, }), }); -exe.root_module.linkSystemLibrary("c", .{}); ``` `src/c.h` 中放需要翻译的 C 头文件: @@ -74,13 +89,13 @@ exe.root_module.linkSystemLibrary("c", .{}); ::: info 🅿️ 提示 -注意:为了构建这个,我们需要引入 `libc`。在 Zig 0.16 的构建脚本中,可以让对应模块链接 C 标准库,例如 `exe.root_module.linkSystemLibrary("c", .{})`。 +注意:为了构建这个,我们需要引入 `libc`。translate-c 包的 `Translator` 默认会让翻译出的模块链接 libc(`link_libc = true`);如果使用内置的 `addTranslateC`,则需要手动让对应模块链接 C 标准库,例如 `exe.root_module.linkSystemLibrary("c", .{})`。 -因此通常通过 `zig build` 驱动这个例子;旧的 `@cImport` 单文件代码才适合手动 `zig build-exe source.zig -lc`。 +构建系统内置的 `b.addTranslateC` 在 0.17 中仍然可用,但已被标记为 deprecated。本教程仓库的根构建脚本为了保持零外部依赖,暂时仍使用它来构建这个例子。 ::: -`@cImport` 仍然保留,但在 Zig 0.16 已进入 deprecated 迁移期;它只适合维护旧的单文件示例或历史代码。新代码不要再把它作为 C 头文件入口,应优先使用上面的 `build.zig` + `@import("c")` 路径。 +如果你还在维护使用 `@cImport` 的旧代码,可以参考 [0.17.0 升级指南](../update/upgrade-0.17.0#c-翻译迁移到独立的-translate-c-包) 进行迁移。 ## vcpkg C Lib 导入 @@ -95,9 +110,13 @@ exe.root_module.linkSystemLibrary("c", .{}); 那么在 `build.zig` 文件中, +<<<@/code/release/import_vcpkg/build.zig#translate_c + +并为可执行文件添加 lib 搜索目录与需要链接的库: + <<<@/code/release/import_vcpkg/build.zig#c_import -假设你想要借用 `gsl` 库来对数值进行傅里叶变换,Zig 0.16 新代码应沿用上面的 `addTranslateC` + `@import("c")` 路径。下面这个历史片段仍使用旧的 `@cImport`,仅用于说明要导入的 GSL 头文件,不作为新项目推荐写法: +假设你想要借用 `gsl` 库来对数值进行傅里叶变换,那么在 Zig 代码中直接导入上面翻译得到的 `gsl` 模块即可: <<<@/code/release/import_vcpkg/src/main.zig#import_gsl @@ -132,18 +151,16 @@ Zig 提供了一个命令行工具 `zig translate-c` 供我们使用,它可以 ### 构建系统 `translate-c` 与命令行 `translate-c` -构建系统里的 `addTranslateC` 是 Zig 0.16 推荐的头文件导入路径;命令行 `zig translate-c` 更适合一次性查看或手动修改翻译后的代码,例如:将 `anytype` 修改为更加精确的类型、将 `[*c]T` 指针修改为 `[*]T` 或者 `*T` 来提高类型安全性、启动或者禁用某些运行时的安全性功能。 +在构建系统中使用 translate-c 包(或已弃用的内置 `addTranslateC`)是 Zig 0.17 推荐的头文件导入路径;命令行 `zig translate-c` 更适合一次性查看或手动修改翻译后的代码,例如:将 `anytype` 修改为更加精确的类型、将 `[*c]T` 指针修改为 `[*]T` 或者 `*T` 来提高类型安全性、启动或者禁用某些运行时的安全性功能。 ## C 翻译缓存 -C 翻译功能(通过 `build.zig` 的 `addTranslateC` 或 `zig translate-c` 使用)与 Zig 缓存系统集成。使用相同源文件、目标和 `cflags` 的后续构建将使用缓存,而不是重复翻译相同的代码;构建缓存目录是项目下的 `.zig-cache`。 +C 翻译功能(通过 `build.zig` 中的 translate-c 包、`addTranslateC` 或 `zig translate-c` 使用)与 Zig 缓存系统集成。使用相同源文件、目标和 `cflags` 的后续构建将使用缓存,而不是重复翻译相同的代码;构建缓存目录是项目下的 `.zig-cache`。 -下面这个 Zig 文件片段是 `addTranslateC` 生成模块后的调用侧: +下面这个 Zig 文件片段是在构建脚本中生成名为 `c` 的模块后的调用侧: <<<@/code/release/interact_with_c.zig#cTranslate -如果你还在维护旧的 `@cImport` 代码,`--verbose-cimport` 可以临时用于查看旧导入缓存位置,便于迁移或排查。 - ## C 翻译错误 针对某些 C 的结构,zig 会无法翻译,如:`goto`、使用位域(**bitfields**)的结构体、拼接(**token-pasting**)宏,zig 会暂时简单处理一下它们以继续翻译任务。 diff --git a/course/advanced/memory_manage.md b/course/advanced/memory_manage.md index f0d18a80..d06b48e0 100644 --- a/course/advanced/memory_manage.md +++ b/course/advanced/memory_manage.md @@ -10,13 +10,13 @@ outline: deep 事实上,Zig 本身的标准库为我们提供了多种内存分配模型: -1. [`DebugAllocator`](https://ziglang.org/documentation/master/std/#std.heap.debug_allocator.DebugAllocator) +1. [`SafeAllocator`](https://ziglang.org/documentation/master/std/#std.heap.SafeAllocator) 2. [`SmpAllocator`](https://ziglang.org/documentation/master/std/#std.heap.SmpAllocator) 3. [`FixedBufferAllocator`](https://ziglang.org/documentation/master/std/#std.heap.FixedBufferAllocator) 4. [`ArenaAllocator`](https://ziglang.org/documentation/master/std/#std.heap.arena_allocator.ArenaAllocator) 5. [`c_allocator`](https://ziglang.org/documentation/master/std/#std.heap.c_allocator) 6. [`page_allocator`](https://ziglang.org/documentation/master/std/#std.heap.page_allocator) -7. [`StackFallbackAllocator`](https://ziglang.org/documentation/master/std/#std.heap.StackFallbackAllocator) +7. [`BufferFirstAllocator`](https://ziglang.org/documentation/master/std/#std.heap.BufferFirstAllocator) 除了这七种内存分配模型外,还提供了内存池的功能 [`MemoryPool`](https://ziglang.org/documentation/master/std/#std.heap.memory_pool.MemoryPool) @@ -38,17 +38,22 @@ outline: deep ::: -## `DebugAllocator` +## `SafeAllocator` -这是一个用于调试的分配器,现阶段适用于在调试模式下使用该分配器,它的性能并不高! +这是一个以安全为目标的分配器,适合在调试模式下使用,它的性能并不高! -这个分配器的目的不是为了性能,而是为了安全。默认配置下它支持线程安全、安全检查、泄漏检测等能力,并且这些特性都可以按需配置。 +Zig 0.17 用 `SafeAllocator` 取代了原来的 `DebugAllocator`(旧名称仍可使用但已被标记为 deprecated)。它不再是一个需要传入配置的泛型类型,而是一个普通结构体: -<<<@/code/release/memory_manager.zig#DebugAllocator +- 通过 `.init(backing_allocator, options)` 初始化,需要显式提供一个后备分配器(backing allocator),例如 `std.heap.page_allocator`; +- 始终是线程安全的; +- `deinit` 会报告并释放所有泄漏的内存,返回值为泄漏的数量(`usize`),不再是 `.ok` / `.leak` 枚举; +- 能够检测重复释放、分配大小不匹配、跨实例释放等问题,在调试模式下默认还会检测释放后写入(write after free)。 + +<<<@/code/release/memory_manager.zig#SafeAllocator ## `SmpAllocator` -专为 `ReleaseFast` 优化设计的分配器,启用多线程。 +专为 `fast` 构建模式设计的分配器,支持多线程。 这个分配器是一个单例;它使用全局状态,并且整个过程只应实例化一个。 @@ -66,10 +71,14 @@ outline: deep ## `FixedBufferAllocator` -这个分配器是固定大小的内存缓冲区,无法扩容,常常在你需要缓冲某些东西时使用。注意默认情况下它不是线程安全的;而在 Zig 0.16 中,旧的线程安全包装分配器已被移除。如果只是需要线程安全分配,优先使用更适合并发场景的 `SmpAllocator`;如果必须跨线程共享同一个 `FixedBufferAllocator`,应在调用方使用与执行模型匹配的锁(例如 `std.Io.Mutex`)保护临界区。 +这个分配器是固定大小的内存缓冲区,无法扩容,常常在你需要缓冲某些东西时使用。注意 `allocator()` 返回的接口不是线程安全的。 <<<@/code/release/memory_manager.zig#FixedBufferAllocator +通用的 `ThreadSafeAllocator` 包装器在 Zig 0.16 中已被移除,如果需要跨线程共享同一个 `FixedBufferAllocator`,可以改用它自带的 `threadSafeAllocator()`,它提供了一个无锁的线程安全接口。注意不要同时混用 `allocator()` 和 `threadSafeAllocator()` 返回的接口。 + +<<<@/code/release/memory_manager.zig#ThreadSafeFixedBufferAllocator + ## `ArenaAllocator` 这个分配器的特点是你可以多次申请内存,并无需每次用完时进行 `free` 操作,可以使用 `deinit` 直接一次回收所有分发出去的内存,如果你的程序是一个命令行程序或者没有什么特别的循环模式,例如 web server 或者游戏事件循环之类的,那么推荐你使用这个。 @@ -100,13 +109,15 @@ outline: deep <<<@/code/release/memory_manager.zig#page_allocator -## `StackFallbackAllocator` +## `BufferFirstAllocator` -该分配器比较特殊,它会尽量在使用栈上的内存,如果请求的内存量超过了可用的栈空间,那么它将回退到事先制定的分配器,即使用堆内存。 +该分配器比较特殊,它会优先从给定的缓冲区(通常位于栈上)中分配内存,如果缓冲区剩余空间不足,那么它将回退到事先指定的分配器,即使用堆内存。 该分配器的目的和内存池类似,都是尽量避免使用堆内存(堆内存相对于栈上分配过慢)。 -<<<@/code/release/memory_manager.zig#stack_fallback_allocator +Zig 0.17 将原来的 `std.heap.stackFallback` / `StackFallbackAllocator` 重做为 `BufferFirstAllocator`:缓冲区不再作为类型参数内置在分配器中,而是由调用者自行准备后传入 `.init(buffer, fallback_allocator)`。 + +<<<@/code/release/memory_manager.zig#buffer_first_allocator ## `MemoryPool` @@ -129,4 +140,4 @@ outline: deep 待添加,当前你可以通过实现 `Allocator` 接口来实现自己的分配器。为了做到这一点,必须仔细阅读 [`std/mem.zig`](https://github.com/ziglang/zig/blob/master/lib/std/mem.zig) 中的文档注释,然后提供 `allocFn` 和 `resizeFn`。 -有许多分配器示例可供查看以获取灵感。查看 [`std/heap.zig`](https://github.com/ziglang/zig/blob/master/lib/std/heap.zig) 和 [`std.heap.DebugAllocator`](https://github.com/ziglang/zig/blob/master/lib/std/heap/debug_allocator.zig) +有许多分配器示例可供查看以获取灵感。查看 [`std/heap.zig`](https://github.com/ziglang/zig/blob/master/lib/std/heap.zig) 和 [`std.heap.SafeAllocator`](https://github.com/ziglang/zig/blob/master/lib/std/heap/SafeAllocator.zig) diff --git a/course/advanced/reflection.md b/course/advanced/reflection.md index faec1d91..b6b7f20e 100644 --- a/course/advanced/reflection.md +++ b/course/advanced/reflection.md @@ -54,19 +54,21 @@ main.T.Y [`@typeInfo`](https://ziglang.org/documentation/master/#typeInfo),该内建函数用于获取类型的信息。 -该函数返回一个 [`std.builtin.Type`](https://ziglang.org/documentation/master/std/#std.builtin.Type),它包含了此类型的所有信息。 +该函数返回一个 [`std.lang.Type`](https://ziglang.org/documentation/master/std/#std.lang.Type)(Zig 0.17 起 `std.builtin` 更名为 `std.lang`,旧名称仍可使用但已被标记为 deprecated),它包含了此类型的所有信息。 它是一个联合类型,使用小写的联合标签来表示具体类型信息;遇到 Zig 关键字时需要使用转义字段名,例如 `@"struct"`、`@"union"`、`@"enum"`,整数类型则是 `.int`。要判断类型的种类,可以使用 `switch` 或直接访问相应标签来断言之。 对结构、联合、枚举和错误集合,它保证信息中字段的顺序与源码中出现的顺序相同。 -对结构、联合、枚举和透明类型,它保证信息中声明的顺序与源码中出现的顺序相同。 +对结构、联合、枚举和透明类型,它保证信息中声明的顺序与源码中出现的顺序相同(`decl_names` 中只包含 `pub` 声明)。 + +从 Zig 0.17 开始,结构体、联合、枚举等类型信息采用了**数组结构体(Struct-Of-Arrays)**风格:不再提供由 `StructField` 组成的 `fields` 数组,而是把字段名、字段类型和字段属性分别存放在 `field_names`、`field_types`、`field_attrs`(枚举则是 `field_names` 与 `field_values`)中,这与 `@Struct` 等内建函数的参数形式保持一致。 如以下示例中,首先使用 `@typeInfo` 来获取类型 `T` 的信息,然后将其断言为一个 `@"struct"` 类型,最后用 `inline for` 输出其字段值。 <<<@/code/release/reflection.zig#typeInfo -需要注意的是,示例必须使用 `inline for` 才能编译通过,这是因为我们读取了每个字段的 `type`。在 Zig 0.16 中,`std.builtin.Type.StructField` 本身可以作为运行时大小的字段信息读取;只有像字段类型这样的 comptime-only 信息,才需要在编译期用 `inline for` 处理。 +需要注意的是,示例必须使用 `inline for` 才能编译通过,这是因为我们读取了每个字段的类型(`field_types`),它是 comptime-only 的信息;而 `field_names` 只是字符串切片,可以在运行时读取。 ::: warning @@ -78,7 +80,7 @@ main.T.Y <<<@/code/release/reflection.zig#TypeInfo2 -在以下示例中,使用 `@typeInfo` 获得一个结构体的信息,并使用 `@Struct` 构造一个新的类型。构造的新结构体类型和原结构体的字段名和顺序相同,但结构体的内存布局被改为 extern,且每个字段的对齐被改为 1。 +在以下示例中,使用 `@typeInfo` 获得一个结构体的信息,并使用 `@Struct` 构造一个新的类型。构造的新结构体类型和原结构体的字段名和顺序相同,但结构体的内存布局被改为 extern,且每个字段的对齐被改为 1。由于 0.17 的类型信息与 `@Struct` 的参数形式一致,字段名与字段类型可以直接复用,只需要重新准备字段属性。 <<<@/code/release/reflection.zig#TypeInfo3 @@ -88,7 +90,7 @@ main.T.Y [`@hasDecl`](https://ziglang.org/documentation/master/#hasDecl) 用于返回一个容器中是否包含指定名字的声明。 -完全是编译期计算的,故值也是编译期已知的。 +完全是编译期计算的,故值也是编译期已知的。注意,从 Zig 0.17 开始,`@hasDecl` 只会对 `pub` 声明返回 `true`;在此之前,与 `@hasDecl` 处于同一文件中的非 `pub` 声明也会返回 `true`。 <<<@/code/release/reflection.zig#hasDecl diff --git a/course/advanced/result-location.md b/course/advanced/result-location.md index 4c13a1c7..ca0d6b25 100644 --- a/course/advanced/result-location.md +++ b/course/advanced/result-location.md @@ -102,13 +102,17 @@ outline: deep 从 Zig 0.14.0 开始,标准库中的许多类型都采用了声明字面量模式。 -### ArrayListUnmanaged +### ArrayList + +Zig 0.15.1 起 `std.ArrayList` 默认不再保存分配器(unmanaged),原来的 `std.ArrayListUnmanaged` 只是它的别名,并已被标记为 deprecated。它的空状态通过 `.empty` 声明字面量获得: <<<@/code/release/result-location.zig#stdlib_arraylist -### DebugAllocator +### SafeAllocator + +Zig 0.17 中取代 `DebugAllocator` 的 `SafeAllocator` 需要传入后备分配器,因此它的 `init` 是一个函数,通过 `.init(...)` 这种调用函数的声明字面量进行初始化: -<<<@/code/release/result-location.zig#stdlib_debug_allocator +<<<@/code/release/result-location.zig#stdlib_safe_allocator ## 字段和声明不可重名 diff --git a/course/advanced/type_cast.md b/course/advanced/type_cast.md index 5915fea0..ce3166d8 100644 --- a/course/advanced/type_cast.md +++ b/course/advanced/type_cast.md @@ -108,15 +108,15 @@ undefined 是一个神奇的值,它可以赋值给所有类型,代表这个 显式强制转换是通过内建函数完成的,有些转换是安全的,有些是执行语言级断言,有些转换在运行时无操作。 -- [`@bitCast`](https://ziglang.org/documentation/master/#bitCast) 更改类型但保持位不变 +- [`@bitCast`](https://ziglang.org/documentation/master/#bitCast) 更改类型但保持位不变。Zig 0.17 起它重新解释的是值的**逻辑位表示**:涉及数组或向量时,各元素的位按顺序从低位到高位拼接,结果与目标端序无关;同时不再允许对 `extern struct`、`extern union` 使用 `@bitCast`,需要按内存布局重新解释时请改用 `@ptrCast` 或 `extern union` - [`@alignCast`](https://ziglang.org/documentation/master/#alignCast) 显式强制转换对齐 -- [`@enumFromInt`](https://ziglang.org/documentation/master/#enumFromInt) 根据整数值获取对应的枚举值 +- [`@fromBackingInt`](https://ziglang.org/documentation/master/#fromBackingInt) 根据底层整数值获取对应的枚举值(或带显式底层整数类型的 `packed struct`),Zig 0.17 起取代已弃用的 `@enumFromInt` - [`@errCast`](https://ziglang.org/documentation/master/#errorCast) 显式强制转换为错误的子集 - [`@floatCast`](https://ziglang.org/documentation/master/#floatCast) 将大浮点数转为小浮点数 - [`@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) 获取枚举值或联合标记对应的整数值 +- [`@backingInt`](https://ziglang.org/documentation/master/#backingInt) 获取枚举值、联合标记或带显式底层整数类型的 `packed struct` 对应的底层整数值,Zig 0.17 起取代已弃用的 `@intFromEnum` - [`@intFromError`](https://ziglang.org/documentation/master/#intFromError) 获取对应错误的整数值 - [`@trunc`](https://ziglang.org/documentation/master/#trunc) 将浮点数向零取整;结果类型由上下文决定,可以直接得到整数类型。需要其他舍入方式时使用 `@floor`、`@ceil` 或 `@round` - [`@intFromPtr`](https://ziglang.org/documentation/master/#intFromPtr) 获取指针指向的地址(整数 `usize`),这在嵌入式开发和内核开发时很常用 diff --git a/course/advanced/undefined_behavior.md b/course/advanced/undefined_behavior.md index 46772013..6fcfec0a 100644 --- a/course/advanced/undefined_behavior.md +++ b/course/advanced/undefined_behavior.md @@ -11,7 +11,7 @@ zig 本身有许多未定义行为,它们可以很方便地帮助开发者找 > [!WARNING] > 注意:本章节并没有 CI 检查,故可能存在内容过期的情况,具体可参考 [官方手册](https://ziglang.org/documentation/master/#Undefined-Behavior)。 -安全检查会在 debug、ReleaseSafe 模式下开启,但可以使用 [`@setRuntimeSafety`](https://ziglang.org/documentation/master/#setRuntimeSafety) 来强制指定在单独的块中是否开启安全检查(这将忽略构建模式)。 +安全检查会在 `debug`、`safe` 模式下开启,但可以使用 [`@setRuntimeSafety`](https://ziglang.org/documentation/master/#setRuntimeSafety) 来强制指定在单独的块中是否开启安全检查(这将忽略构建模式)。 当出现安全检查失败时,zig 会编译失败并触发堆栈跟踪: @@ -153,7 +153,7 @@ pub fn main() void { ## 无效枚举转换 -当使用 [`@enumFromInt`](https://ziglang.org/documentation/master/#enumFromInt) 来获取枚举时,如果没有对应整数的枚举,那么会导致程序或者编译器报告错误! +当使用 [`@fromBackingInt`](https://ziglang.org/documentation/master/#fromBackingInt)(Zig 0.17 之前为 `@enumFromInt`)来获取枚举时,如果没有对应整数的枚举,那么会导致程序或者编译器报告错误! ## 无效错误集合转换 diff --git a/course/basic/advanced_type/array.md b/course/basic/advanced_type/array.md index 2e804dde..153855af 100644 --- a/course/basic/advanced_type/array.md +++ b/course/basic/advanced_type/array.md @@ -66,9 +66,12 @@ outline: deep ::: -### 乘法 +### 重复与填充 -可以使用 `**` 对数组进行乘法操作。运算符左侧是数组,右侧是重复的次数,最终会生成一个更长的数组。 +在 Zig 0.17 之前,可以使用 `**` 对数组进行“乘法”操作(例如 `[_]u8{0} ** 4`)。Zig 0.17 已经**移除**了 `**` 运算符,替代方式有两种: + +- 用同一个值填充整个数组:使用 [`@splat`](https://ziglang.org/documentation/master/#splat),它会根据结果类型推断数组长度; +- 重复一个已有的数组:在编译期使用 `++` 进行串联。 <<<@/code/release/array.zig#multiply @@ -84,7 +87,7 @@ outline: deep ### 使用函数初始化数组 -我们可以使用函数来初始化数组。该函数需要返回数组的单个元素或整个数组。 +我们可以使用函数来初始化数组。该函数需要返回数组的单个元素或整个数组。如果函数返回的是单个元素,可以配合 `@splat` 用它来填充整个数组。 <<<@/code/release/array.zig#func_init_array diff --git a/course/basic/advanced_type/enum.md b/course/basic/advanced_type/enum.md index 026f9d95..44e98560 100644 --- a/course/basic/advanced_type/enum.md +++ b/course/basic/advanced_type/enum.md @@ -56,9 +56,11 @@ Zig 允许我们定义非详尽枚举,即在定义时无需列出所有可能 :::info 🅿️ 提示 -`@enumFromInt` 能够将整数转换为枚举值。但需要注意,如果所选枚举类型中没有表示该整数的值,就会导致[未定义行为](../../advanced/undefined_behavior#无效枚举转换)。 +Zig 0.17 新增了 `@fromBackingInt` 和 `@backingInt`,分别取代了已被标记为 deprecated 的 `@enumFromInt` 与 `@intFromEnum`(`zig fmt` 会自动完成这一替换)。 -如果目标枚举类型是非详尽枚举,那么除了涉及 `@intCast` 相关的安全检查之外,`@enumFromInt` 始终能够得到有效的枚举值。 +`@fromBackingInt` 能够将整数转换为枚举值,它的结果类型通过结果位置推断,参数必须**恰好**是该枚举的标记类型(例如 `u4`),因此其他整数类型需要先用 `@intCast` 转换。需要注意,如果所选枚举类型中没有表示该整数的值,就会导致[未定义行为](../../advanced/undefined_behavior#无效枚举转换)。 + +如果目标枚举类型是非详尽枚举,那么 `@fromBackingInt` 始终能够得到有效的枚举值。反过来,`@backingInt` 返回值的类型就是枚举的标记类型。 ::: diff --git a/course/basic/advanced_type/opaque.md b/course/basic/advanced_type/opaque.md index bf558351..0e9c577c 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 {}; -// C 头文件推荐在 build.zig 中用 addTranslateC 翻译后,再在 Zig 代码里 @import("c")。 +// C 头文件推荐在 build.zig 中借助官方 translate-c 包翻译为模块后,再在 Zig 代码里 @import("c")。 // 使用不透明指针 fn readFile(file: *FILE) void { diff --git a/course/basic/advanced_type/pointer.md b/course/basic/advanced_type/pointer.md index ae3c02d7..9a7a627f 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`。在 Zig 0.16 的构建脚本中,可以让对应模块链接 C 标准库,例如 `exe.root_module.linkSystemLibrary("c", .{})`。 +以上代码编译需要额外链接 `libc`。从 Zig 0.16 起,可以在构建脚本中让对应模块链接 C 标准库,例如 `exe.root_module.linkSystemLibrary("c", .{})`。 ::: @@ -189,7 +189,9 @@ Zig 支持指针的加减运算,但建议在进行运算前,将指针转换 ::: -在 Zig 中,指针类型也具有对齐值。如果该值等于其基础类型的对齐方式,则可以从类型声明中省略它: +在 Zig 中,指针类型也具有对齐值。如果该值等于其基础类型的对齐方式,则可以从类型声明中省略它。 + +需要注意,从 Zig 0.16 起,即便对齐值相同,显式写出 `align` 的指针类型(如 `*align(4) i32`)与省略 `align` 的指针类型(如 `*i32`)也不再是同一个类型,但二者之间可以相互隐式转换。另外,Zig 0.17 将指针的 `const`、`volatile`、`allowzero`、地址空间和对齐等属性统一放进了 `@typeInfo(T).pointer.attrs` 中,未显式指定对齐时 `attrs.@"align"` 为 `null`: <<<@/code/release/pointer.zig#align diff --git a/course/basic/advanced_type/struct.md b/course/basic/advanced_type/struct.md index 6c93a4c8..7591efb7 100644 --- a/course/basic/advanced_type/struct.md +++ b/course/basic/advanced_type/struct.md @@ -180,7 +180,7 @@ :::info 🅿️ 提示 -元组还有一个与数组相同的 `len` 字段,并且支持 `++` 和 `**` 运算符,以及[内联 for](../process_control/loop.md#内联-inline)。 +元组还有一个与数组相同的 `len` 字段,并且支持 `++` 运算符(Zig 0.17 移除了 `**` 运算符),以及[内联 for](../process_control/loop.md#内联-inline)。 ::: @@ -226,7 +226,7 @@ ::: -2. 可以使用位转换 [`@bitCast`](https://ziglang.org/documentation/master/#bitCast) 和指针转换 [`@ptrCast`](https://ziglang.org/documentation/master/#ptrCast) 来强制对 `packed` 结构体进行类型转换: +2. 可以使用位转换 [`@bitCast`](https://ziglang.org/documentation/master/#bitCast) 和指针转换 [`@ptrCast`](https://ziglang.org/documentation/master/#ptrCast) 来强制对 `packed` 结构体进行类型转换。注意从 Zig 0.17 开始,`@bitCast` 的结果与目标架构的端序无关(数组的第一个元素对应最低有效位);如果需要观察内存中真实的字节排列,可以使用 `std.mem.toBytes`: :::details 示例 diff --git a/course/basic/advanced_type/vector.md b/course/basic/advanced_type/vector.md index 796faa12..32371052 100644 --- a/course/basic/advanced_type/vector.md +++ b/course/basic/advanced_type/vector.md @@ -44,7 +44,7 @@ Zig 支持最大 `2^32 - 1` 的向量长度。请注意,过长的向量长度 ## `@reduce` -`@reduce(comptime op: std.builtin.ReduceOp, value: anytype) E` +`@reduce(comptime op: std.lang.ReduceOp, value: anytype) E` 使用传入的运算符对向量进行水平归约(_sequential horizontal reduction_),最终得到一个标量。 diff --git a/course/basic/basic_type/function.md b/course/basic/basic_type/function.md index c66f0db9..edb9a6bb 100644 --- a/course/basic/basic_type/function.md +++ b/course/basic/basic_type/function.md @@ -155,7 +155,7 @@ closure() # 输出:Hello, World! ### `@branchHint(.cold)` -`@branchHint(comptime hint: std.builtin.BranchHint) void` +`@branchHint(hint: std.lang.BranchHint) void` 使用 `@branchHint(.cold)` 告诉优化器当前分支或函数很少被调用(或不被调用)。 @@ -167,4 +167,4 @@ closure() # 输出:Hello, World! <<<@/code/release/function.zig#shiftLeftOne -关于可用的调用约定格式,请参考[`std.builtin.CallingConvention`](https://ziglang.org/documentation/master/std/#std.builtin.CallingConvention)。 +关于可用的调用约定格式,请参考[`std.lang.CallingConvention`](https://ziglang.org/documentation/master/std/#std.lang.CallingConvention)。 diff --git a/course/basic/basic_type/number.md b/course/basic/basic_type/number.md index 1b0e5522..8a26c0b6 100644 --- a/course/basic/basic_type/number.md +++ b/course/basic/basic_type/number.md @@ -45,7 +45,7 @@ ### 除零 -Zig 编译器会在编译期和运行时(`ReleaseSmall` 构建模式除外)对除零操作进行检测。编译时检测到错误会直接停止编译;运行时如果发生除零,则会给出完整的堆栈跟踪。 +Zig 编译器会在编译期和运行时(`small` 构建模式除外)对除零操作进行检测。编译时检测到错误会直接停止编译;运行时如果发生除零,则会给出完整的堆栈跟踪。 ::: details 小细节 这里的“除零”包括了除法和求余两种操作。 @@ -184,7 +184,8 @@ pub fn main() void { - `*|`:饱和乘法。乘法结果不会超过该类型的最大值或最小值。 - `<<|`:饱和左移。左移结果不会超过该类型的最大值。 - `++`:数组串联。要求两个数组的元素类型相同。 -- `**`:数组重复。在编译期已知数组的长度和重复次数。 + +> Zig 0.17 之前还有 `**`(数组重复)运算符,现已被移除,请改用 `@splat` 或在编译期通过 `++` 拼接,详见 [数组](../advanced_type/array.md#重复与填充)。 运算的优先级: diff --git a/course/basic/define-variable.md b/course/basic/define-variable.md index 861680ff..d9edd722 100644 --- a/course/basic/define-variable.md +++ b/course/basic/define-variable.md @@ -104,7 +104,7 @@ Shadow(遮蔽)指的是在内部作用域中声明一个与外部作用域 ### 空的块 -空的块等效于 `void{}`,即一个空的函数体。 +空的块 `{}` 的值为 `void`,即一个空的函数体。Zig 0.17 之前还可以写作 `void{}`,该写法现已被移除。 ## 容器 diff --git a/course/basic/error_handle.md b/course/basic/error_handle.md index cfd5d9f1..2ff7fcfa 100644 --- a/course/basic/error_handle.md +++ b/course/basic/error_handle.md @@ -116,7 +116,7 @@ outline: deep 那么如何断言函数不会返回错误呢? -使用 `unreachable`。这会告诉编译器此次函数执行不会返回错误。`unreachable` 在 `Debug` 和 `ReleaseSafe` 模式下会触发恐慌(panic),而在 `ReleaseFast` 和 `ReleaseSmall` 模式下会产生未定义行为。因此,当调试应用程序时,如果函数执行到这里,就会发生 `panic`。 +使用 `unreachable`。这会告诉编译器此次函数执行不会返回错误。`unreachable` 在 `debug` 和 `safe` 模式下会触发恐慌(panic),而在 `fast` 和 `small` 模式下会产生未定义行为。因此,当调试应用程序时,如果函数执行到这里,就会发生 `panic`。 <<<@/code/release/error_handle.zig#AssertNoError @@ -144,7 +144,7 @@ outline: deep `errdefer` 可以看作是 `defer` 的一个特殊变体,它用于处理错误,仅在函数作用域返回错误时,才会执行 `errdefer`。 -还可以使用捕获语法来捕获错误,这对于在清理期间打印错误信息很有用。 +在 Zig 0.17 之前,还可以使用 `errdefer |err| { ... }` 这样的捕获语法来获取错误值。Zig 0.17 已经**移除**了这种捕获语法:如果需要在清理时观察具体的错误,可以把函数拆成两层,在内层继续使用不带捕获的 `errdefer` 做清理,在外层通过 `catch |err|` 获取错误。 <<<@/code/release/error_handle.zig#DeferErrorCapture diff --git a/course/basic/process_control/unreachable.md b/course/basic/process_control/unreachable.md index 27bc8fa7..7b297e15 100644 --- a/course/basic/process_control/unreachable.md +++ b/course/basic/process_control/unreachable.md @@ -8,8 +8,8 @@ outline: deep ## 构建模式下的行为 -- 在 `Debug` 和 `ReleaseSafe` 模式下,`unreachable` 会触发 `panic`,并报告"不可达代码"错误,帮助开发者发现逻辑漏洞。 -- 在 `ReleaseFast` 和 `ReleaseSmall` 模式下,编译器会**假定**永远不会执行到 `unreachable` 处,从而对代码进行优化(例如消除死代码分支)。如果程序实际运行到此处,则是未定义行为。 +- 在 `debug` 和 `safe` 模式下,`unreachable` 会触发 `panic`,并报告"不可达代码"错误,帮助开发者发现逻辑漏洞。 +- 在 `fast` 和 `small` 模式下,编译器会**假定**永远不会执行到 `unreachable` 处,从而对代码进行优化(例如消除死代码分支)。如果程序实际运行到此处,则是未定义行为。 ## 使用场景 diff --git a/course/basic/union.md b/course/basic/union.md index 3929ea8f..4b8ed2cf 100644 --- a/course/basic/union.md +++ b/course/basic/union.md @@ -46,7 +46,7 @@ outline: deep 简单来说,标记联合可以明确辨别当前存储的类型,使用起来更方便。 -而普通联合类型在 `ReleaseSmall` 和 `ReleaseFast` 构建模式下,将无法检测出错误的读取行为。例如,将一个 `u64` 存储在一个 `union` 中,然后尝试将其读取为一个 `f64`,这在程序员看来是非法的,但在这些构建模式下运行时却可能不会报错! +而普通联合类型在 `small` 和 `fast` 构建模式下,将无法检测出错误的读取行为。例如,将一个 `u64` 存储在一个 `union` 中,然后尝试将其读取为一个 `f64`,这在程序员看来是非法的,但在这些构建模式下运行时却可能不会报错! ::: diff --git a/course/basic/zero-type.md b/course/basic/zero-type.md index d860b700..8f06d094 100644 --- a/course/basic/zero-type.md +++ b/course/basic/zero-type.md @@ -22,7 +22,7 @@ var map = std.AutoHashMap(i32, void).init(std.testing.allocator); ## 整数 -[整数](../basic/basic_type/number.md) 声明可以使用 `u0` 和 `i0` 来声明**零大小整数类型**,它们的大小也是 0 bit。 +[整数](../basic/basic_type/number.md) 声明可以使用 `u0` 来声明**零大小整数类型**,它的大小也是 0 bit。Zig 0.17 移除了 `i0`,原来使用 `i0` 的地方基本都可以直接换成 `u0`。 ## 数组和切片 diff --git a/course/code/17/array.zig b/course/code/17/array.zig new file mode 100644 index 00000000..6c997674 --- /dev/null +++ b/course/code/17/array.zig @@ -0,0 +1,161 @@ +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 { + // 0.17 移除了数组乘法语法 `**` + // 用同一个值填充整个数组,使用 @splat + const zeros: [5]i8 = @splat(0); + print("{any}\n", .{zeros}); // [5]i8{ 0, 0, 0, 0, 0 } + + // 重复一个数组,可以在编译期用 ++ 串联 + const small = [3]i8{ 1, 2, 3 }; + const big: [9]i8 = small ++ small ++ small; + 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 { + // 0.17 起使用 @splat 代替 `[_]i32{make(3)} ** 10` + const array: [10]i32 = @splat(make(3)); + 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; + const assert = @import("std").debug.assert; + + pub fn main() void { + const fancy_array = comptime init: { + var initial_value: [10]usize = undefined; + for (&initial_value, 0..) |*pt, i| { + pt.* = i; + } + break :init initial_value; + }; + comptime assert(fancy_array[9] == 9); + print("{any}\n", .{fancy_array}); + } + // #endregion comptime_init_array +}; + +test "multiply" { + const std = @import("std"); + const zeros: [5]i8 = @splat(0); + try std.testing.expectEqualSlices(i8, &.{ 0, 0, 0, 0, 0 }, &zeros); + + const small = [3]i8{ 1, 2, 3 }; + const big: [9]i8 = small ++ small ++ small; + try std.testing.expectEqualSlices(i8, &.{ 1, 2, 3, 1, 2, 3, 1, 2, 3 }, &big); +} + +test "func init array" { + const std = @import("std"); + const array: [10]i32 = @splat(FuncInitArray.make(3)); + for (array) |item| try std.testing.expectEqual(4, item); +} diff --git a/course/code/17/assembly.zig b/course/code/17/assembly.zig new file mode 100644 index 00000000..d6efe2ef --- /dev/null +++ b/course/code/17/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/17/atomic.zig b/course/code/17/atomic.zig new file mode 100644 index 00000000..acdc0712 --- /dev/null +++ b/course/code/17/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/17/build_system/README.md b/course/code/17/build_system/README.md new file mode 100644 index 00000000..4aaf9c5f --- /dev/null +++ b/course/code/17/build_system/README.md @@ -0,0 +1 @@ +该文件夹是构建系统的示例文件! diff --git a/course/code/17/build_system/basic/build.zig b/course/code/17/build_system/basic/build.zig new file mode 100644 index 00000000..69ae8244 --- /dev/null +++ b/course/code/17/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/17/build_system/basic/build.zig.zon b/course/code/17/build_system/basic/build.zig.zon new file mode 100644 index 00000000..8553e004 --- /dev/null +++ b/course/code/17/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/17/build_system/basic/src/main.zig b/course/code/17/build_system/basic/src/main.zig new file mode 100644 index 00000000..a58cdbd6 --- /dev/null +++ b/course/code/17/build_system/basic/src/main.zig @@ -0,0 +1,26 @@ +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" { + const gpa = std.testing.allocator; + var list: std.ArrayList(i32) = .empty; + defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak! + try list.append(gpa, 42); + try std.testing.expectEqual(@as(i32, 42), list.pop()); +} diff --git a/course/code/17/build_system/build.zig b/course/code/17/build_system/build.zig new file mode 100644 index 00000000..481735ec --- /dev/null +++ b/course/code/17/build_system/build.zig @@ -0,0 +1,67 @@ +const std = @import("std"); + +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); + + // 0.17 起 configure 阶段的结果会被缓存,遍历目录前需要声明对目录内容的依赖 + b.dependOnDirectoryContents(b.path(".")); + + // `b.root` 是当前构建根目录(Cache.Path),取代了旧的 `b.build_root` + var dir = try b.root.openDir(io, ".", .{ .iterate = true }); + defer dir.close(io); + + var iterate = dir.iterate(); + while (try iterate.next(io)) |entry| { + if (entry.kind != .directory) continue; + if (entry.name[0] == '.' or std.mem.eql(u8, entry.name, "zig-out")) continue; + + // 0.17 将 configure 与 make 拆成了两个进程,不能再在 build 函数中直接 spawn 子进程, + // 而是把子项目的 `zig build` 声明为 Run 步骤,交给 make 阶段执行 + const sub_build = b.addSystemCommand(&.{ b.graph.zig_exe, "build" }); + sub_build.setName(b.fmt("zig build ({s})", .{entry.name})); + sub_build.setCwd(b.path(entry.name)); + sub_build.stdio = .inherit; + b.getInstallStep().dependOn(&sub_build.step); + + // 演示单元测试的子项目额外执行一次 `zig build test`,确保示例中的测试代码同样能通过 + if (std.mem.eql(u8, entry.name, "test")) { + const sub_test = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "test" }); + sub_test.setName(b.fmt("zig build test ({s})", .{entry.name})); + sub_test.setCwd(b.path(entry.name)); + sub_test.stdio = .inherit; + sub_test.step.dependOn(&sub_build.step); + b.getInstallStep().dependOn(&sub_test.step); + } + } +} diff --git a/course/code/17/build_system/cli/build.zig b/course/code/17/build_system/cli/build.zig new file mode 100644 index 00000000..a1036af5 --- /dev/null +++ b/course/code/17/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/17/build_system/cli/build.zig.zon b/course/code/17/build_system/cli/build.zig.zon new file mode 100644 index 00000000..09eef71d --- /dev/null +++ b/course/code/17/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/17/build_system/cli/src/main.zig b/course/code/17/build_system/cli/src/main.zig new file mode 100644 index 00000000..430559b8 --- /dev/null +++ b/course/code/17/build_system/cli/src/main.zig @@ -0,0 +1,26 @@ +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" { + const gpa = std.testing.allocator; + var list: std.ArrayList(i32) = .empty; + defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak! + try list.append(gpa, 42); + try std.testing.expectEqual(@as(i32, 42), list.pop()); +} diff --git a/course/code/17/build_system/docs/build.zig b/course/code/17/build_system/docs/build.zig new file mode 100644 index 00000000..3ab94831 --- /dev/null +++ b/course/code/17/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/17/build_system/docs/build.zig.zon b/course/code/17/build_system/docs/build.zig.zon new file mode 100644 index 00000000..a40db6b3 --- /dev/null +++ b/course/code/17/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/17/build_system/docs/src/root.zig b/course/code/17/build_system/docs/src/root.zig new file mode 100644 index 00000000..ecfeade1 --- /dev/null +++ b/course/code/17/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/17/build_system/embedfile/build.zig b/course/code/17/build_system/embedfile/build.zig new file mode 100644 index 00000000..9bb665ec --- /dev/null +++ b/course/code/17/build_system/embedfile/build.zig @@ -0,0 +1,43 @@ +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()); + + // 传递参数 + // 0.17 移除了 b.args,改为声明一个“透传参数”占位,make 阶段会替换为 `--` 之后的参数 + run_cmd.addPassthruArgs(); + + // 指定一个 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/17/build_system/embedfile/build.zig.zon b/course/code/17/build_system/embedfile/build.zig.zon new file mode 100644 index 00000000..9fab9793 --- /dev/null +++ b/course/code/17/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/17/build_system/embedfile/src/hello.txt b/course/code/17/build_system/embedfile/src/hello.txt new file mode 100644 index 00000000..b45ef6fe --- /dev/null +++ b/course/code/17/build_system/embedfile/src/hello.txt @@ -0,0 +1 @@ +Hello, World! \ No newline at end of file diff --git a/course/code/17/build_system/embedfile/src/main.zig b/course/code/17/build_system/embedfile/src/main.zig new file mode 100644 index 00000000..467b20ed --- /dev/null +++ b/course/code/17/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/17/build_system/externalfile/build.zig b/course/code/17/build_system/externalfile/build.zig new file mode 100644 index 00000000..2a93af92 --- /dev/null +++ b/course/code/17/build_system/externalfile/build.zig @@ -0,0 +1,62 @@ +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()); + + // 传递参数 + // 0.17 移除了 b.args,改为声明一个“透传参数”占位,make 阶段会替换为 `--` 之后的参数 + run_cmd.addPassthruArgs(); + + // 指定一个 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/17/build_system/externalfile/build.zig.zon b/course/code/17/build_system/externalfile/build.zig.zon new file mode 100644 index 00000000..bded66c1 --- /dev/null +++ b/course/code/17/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/17/build_system/externalfile/src/main.zig b/course/code/17/build_system/externalfile/src/main.zig new file mode 100644 index 00000000..4cab998e --- /dev/null +++ b/course/code/17/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/17/build_system/lib/build.zig b/course/code/17/build_system/lib/build.zig new file mode 100644 index 00000000..2d0b4020 --- /dev/null +++ b/course/code/17/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/17/build_system/lib/build.zig.zon b/course/code/17/build_system/lib/build.zig.zon new file mode 100644 index 00000000..6ca3a749 --- /dev/null +++ b/course/code/17/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/17/build_system/lib/src/main.zig b/course/code/17/build_system/lib/src/main.zig new file mode 100644 index 00000000..34185e55 --- /dev/null +++ b/course/code/17/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/17/build_system/lib/src/root.zig b/course/code/17/build_system/lib/src/root.zig new file mode 100644 index 00000000..ecfeade1 --- /dev/null +++ b/course/code/17/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/17/build_system/main.zig b/course/code/17/build_system/main.zig new file mode 100644 index 00000000..b16fb0fd --- /dev/null +++ b/course/code/17/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/17/build_system/options/build.zig b/course/code/17/build_system/options/build.zig new file mode 100644 index 00000000..dc5af270 --- /dev/null +++ b/course/code/17/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/17/build_system/options/build.zig.zon b/course/code/17/build_system/options/build.zig.zon new file mode 100644 index 00000000..b3a7f357 --- /dev/null +++ b/course/code/17/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/17/build_system/options/src/main.zig b/course/code/17/build_system/options/src/main.zig new file mode 100644 index 00000000..635449e3 --- /dev/null +++ b/course/code/17/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/17/build_system/step/build.zig b/course/code/17/build_system/step/build.zig new file mode 100644 index 00000000..abce0953 --- /dev/null +++ b/course/code/17/build_system/step/build.zig @@ -0,0 +1,43 @@ +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 + // 当前是将参数传递给运行构建结果 + // 0.17 移除了 b.args,改为声明一个“透传参数”占位,make 阶段会替换为 `--` 之后的参数 + run_exe.addPassthruArgs(); + + // 指定一个 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/17/build_system/step/build.zig.zon b/course/code/17/build_system/step/build.zig.zon new file mode 100644 index 00000000..a2461217 --- /dev/null +++ b/course/code/17/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/17/build_system/step/src/main.zig b/course/code/17/build_system/step/src/main.zig new file mode 100644 index 00000000..34185e55 --- /dev/null +++ b/course/code/17/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/17/build_system/system_lib/build.zig b/course/code/17/build_system/system_lib/build.zig new file mode 100644 index 00000000..55ef0097 --- /dev/null +++ b/course/code/17/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/17/build_system/system_lib/build.zig.zon b/course/code/17/build_system/system_lib/build.zig.zon new file mode 100644 index 00000000..6f9b6047 --- /dev/null +++ b/course/code/17/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/17/build_system/system_lib/src/main.zig b/course/code/17/build_system/system_lib/src/main.zig new file mode 100644 index 00000000..1df86419 --- /dev/null +++ b/course/code/17/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/17/build_system/test/build.zig b/course/code/17/build_system/test/build.zig new file mode 100644 index 00000000..48ca138a --- /dev/null +++ b/course/code/17/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/17/build_system/test/build.zig.zon b/course/code/17/build_system/test/build.zig.zon new file mode 100644 index 00000000..92b35441 --- /dev/null +++ b/course/code/17/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/17/build_system/test/src/main.zig b/course/code/17/build_system/test/src/main.zig new file mode 100644 index 00000000..a58cdbd6 --- /dev/null +++ b/course/code/17/build_system/test/src/main.zig @@ -0,0 +1,26 @@ +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" { + const gpa = std.testing.allocator; + var list: std.ArrayList(i32) = .empty; + defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak! + try list.append(gpa, 42); + try std.testing.expectEqual(@as(i32, 42), list.pop()); +} diff --git a/course/code/17/build_system/test/src/root.zig b/course/code/17/build_system/test/src/root.zig new file mode 100644 index 00000000..ecfeade1 --- /dev/null +++ b/course/code/17/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/17/build_system/tinytetris/build.zig b/course/code/17/build_system/tinytetris/build.zig new file mode 100644 index 00000000..3628a12d --- /dev/null +++ b/course/code/17/build_system/tinytetris/build.zig @@ -0,0 +1,58 @@ +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()); + + // 运行时参数传递 + // 0.17 移除了 b.args,改为声明一个“透传参数”占位,make 阶段会替换为 `--` 之后的参数 + run_cmd.addPassthruArgs(); + + // 运行的 step + const run_step = b.step("run", "Run the app"); + // 依赖于前面的运行 + run_step.dependOn(&run_cmd.step); +} diff --git a/course/code/17/build_system/tinytetris/build.zig.zon b/course/code/17/build_system/tinytetris/build.zig.zon new file mode 100644 index 00000000..a8f670af --- /dev/null +++ b/course/code/17/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/17/build_system/tinytetris/src/main.cc b/course/code/17/build_system/tinytetris/src/main.cc new file mode 100644 index 00000000..3018b6a4 --- /dev/null +++ b/course/code/17/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/17/char-and-boolean.zig b/course/code/17/char-and-boolean.zig new file mode 100644 index 00000000..1c88251b --- /dev/null +++ b/course/code/17/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/17/comptime.zig b/course/code/17/comptime.zig new file mode 100644 index 00000000..3c14639a --- /dev/null +++ b/course/code/17/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/17/decision.zig b/course/code/17/decision.zig new file mode 100644 index 00000000..3b47914e --- /dev/null +++ b/course/code/17/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/17/defer.zig b/course/code/17/defer.zig new file mode 100644 index 00000000..7fda8827 --- /dev/null +++ b/course/code/17/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/17/define_variable.zig b/course/code/17/define_variable.zig new file mode 100644 index 00000000..8db019f5 --- /dev/null +++ b/course/code/17/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/17/echo_tcp_server.zig b/course/code/17/echo_tcp_server.zig new file mode 100644 index 00000000..f1b64eaf --- /dev/null +++ b/course/code/17/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/17/enum.zig b/course/code/17/enum.zig new file mode 100644 index 00000000..69e8647d --- /dev/null +++ b/course/code/17/enum.zig @@ -0,0 +1,174 @@ +pub fn main() !void { + try EnumSize.main(); + try EnumReference.main(); + try Non_exhaustiveEnum.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 { + const info = @typeInfo(Small).@"enum"; + try expect(info.tag_type == u2); + // 0.17 起类型信息采用“数组结构体”风格:字段名与字段值分别存放 + try expect(info.field_names.len == 4); + try expect(mem.eql(u8, info.field_names[1], "two")); + try expect(info.field_values[1] == 1); + 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, + _, + }; + + // 明确列出的枚举值 + // 0.17 使用 @fromBackingInt 代替 @enumFromInt + const blue: Color = @fromBackingInt(2); + try expect(blue == .blue); + + // 未列出的枚举值:8 在 u4 的范围内(0~15) + const yellow: Color = @fromBackingInt(8); + try expect(@TypeOf(yellow) == Color); + // 0.17 使用 @backingInt 代替 @intFromEnum,结果类型就是标记类型 u4 + try expect(@backingInt(yellow) == 8); + try expect(@TypeOf(@backingInt(yellow)) == u4); + + // @fromBackingInt 的参数必须恰好是标记类型 u4,42 超出了 u4 的范围,无法通过编译 + // const ub: Color = @fromBackingInt(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 + } +}; + +test "enum" { + try EnumSize.main(); + try EnumReference.main(); + try Non_exhaustiveEnum.main(); +} diff --git a/course/code/17/error_handle.zig b/course/code/17/error_handle.zig new file mode 100644 index 00000000..b6f2b4b0 --- /dev/null +++ b/course/code/17/error_handle.zig @@ -0,0 +1,274 @@ +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 + // 0.17 移除了 `errdefer |err| { ... }` 捕获语法 + // 需要观察错误时,把函数拆成两层,在外层用 catch 捕获 + fn deferErrorCaptureExample() !void { + deferErrorCaptureInner() catch |err| { + std.debug.print("the error is {s}\n", .{@errorName(err)}); + return err; + }; + } + + fn deferErrorCaptureInner() !void { + // 这里依然可以使用不带捕获的 errdefer 做清理 + errdefer std.debug.print("cleanup before returning error\n", .{}); + + return error.DeferError; + } + // #endregion DeferErrorCapture +}; + +test "errdefer capture migration" { + try @import("std").testing.expectError(error.DeferError, ErrDefer.deferErrorCaptureExample()); +} + +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/17/function.zig b/course/code/17/function.zig new file mode 100644 index 00000000..fa5e81de --- /dev/null +++ b/course/code/17/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/17/hello_world.zig b/course/code/17/hello_world.zig new file mode 100644 index 00000000..e5555e34 --- /dev/null +++ b/course/code/17/hello_world.zig @@ -0,0 +1,66 @@ +// 三个 struct 各自包含一段文档片段,每段都是可以直接粘贴进 src/main.zig 的完整程序。 +// 因此 std 的导入放在各 struct 内部,文件级不能再有 `const std`(否则 ambiguous reference)。 +pub fn main(init: @import("std").process.Init) !void { + try One.main(); + try Two.main(init); + try Three.main(init); +} + +const One = struct { + // #region one + const std = @import("std"); + + pub fn main() !void { + std.debug.print("Hello, World!\n", .{}); + } + // #endregion one +}; + +const Two = struct { + // #region two + const std = @import("std"); + + pub fn main(init: std.process.Init) !void { + const io = init.io; + + // 传入长度为 0 的缓冲区,表示不缓冲,每次 print 都直接写出 + var stdout_writer = std.Io.File.stdout().writer(io, &.{}); + const stdout = &stdout_writer.interface; + + var stderr_writer = std.Io.File.stderr().writer(io, &.{}); + const stderr = &stderr_writer.interface; + + try stdout.print("Hello {s}!\n", .{"out"}); + try stderr.print("Hello {s}!\n", .{"err"}); + } + // #endregion two +}; + +const Three = struct { + // #region three + const std = @import("std"); + + pub fn main(init: std.process.Init) !void { + const io = init.io; + + // 定义两个缓冲区 + var stdout_buffer: [1024]u8 = undefined; // [!code focus] + var stderr_buffer: [1024]u8 = undefined; // [!code focus] + + // 获取 writer 句柄 + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); // [!code focus] + const stdout = &stdout_writer.interface; + + var stderr_writer = std.Io.File.stderr().writer(io, &stderr_buffer); // [!code focus] + const stderr = &stderr_writer.interface; + + // 通过句柄写入 buffer + try stdout.print("Hello {s}!\n", .{"out"}); + try stderr.print("Hello {s}!\n", .{"err"}); + + // 把 buffer 中的内容真正刷出去 + try stdout.flush(); // [!code focus] + try stderr.flush(); // [!code focus] + } + // #endregion three +}; diff --git a/course/code/17/import_dependency_build/build.zig b/course/code/17/import_dependency_build/build.zig new file mode 100644 index 00000000..f8303ac5 --- /dev/null +++ b/course/code/17/import_dependency_build/build.zig @@ -0,0 +1,33 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + const io = b.graph.io; + + // 0.17 起 configure 阶段的结果会被缓存,遍历目录前需要声明对目录内容的依赖 + b.dependOnDirectoryContents(b.path(".")); + + // `b.root` 是当前构建根目录(Cache.Path),不依赖命令执行时所在的目录 + var dir = try b.root.openDir(io, ".", .{ .iterate = true }); + defer dir.close(io); + + var iterate = dir.iterate(); + while (try iterate.next(io)) |entry| { + if (entry.kind != .directory) continue; + if (entry.name[0] == '.' or std.mem.eql(u8, entry.name, "zig-out")) continue; + + // 每个子目录都应当是一个带 build.zig 的包 + var entry_dir = try dir.openDir(io, entry.name, .{}); + defer entry_dir.close(io); + entry_dir.access(io, "build.zig", .{}) catch { + std.debug.panic("not found build.zig in {s}", .{entry.name}); + }; + + // 0.17 将 configure 与 make 拆成了两个进程,不能再在 build 函数中直接 spawn 子进程, + // 而是把子项目的 `zig build` 声明为 Run 步骤,并使用当前正在运行的 zig,交给 make 阶段执行 + const sub_build = b.addSystemCommand(&.{ b.graph.zig_exe, "build" }); + sub_build.setName(b.fmt("zig build ({s})", .{entry.name})); + sub_build.setCwd(b.path(entry.name)); + sub_build.stdio = .inherit; + b.getInstallStep().dependOn(&sub_build.step); + } +} diff --git a/course/code/17/import_dependency_build/pkg1/build.zig b/course/code/17/import_dependency_build/pkg1/build.zig new file mode 100644 index 00000000..71478915 --- /dev/null +++ b/course/code/17/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/17/import_dependency_build/pkg1/build.zig.zon b/course/code/17/import_dependency_build/pkg1/build.zig.zon new file mode 100644 index 00000000..6eceed4b --- /dev/null +++ b/course/code/17/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/17/import_dependency_build/pkg2/build.zig b/course/code/17/import_dependency_build/pkg2/build.zig new file mode 100644 index 00000000..020bb13d --- /dev/null +++ b/course/code/17/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/17/import_dependency_build/pkg2/build.zig.zon b/course/code/17/import_dependency_build/pkg2/build.zig.zon new file mode 100644 index 00000000..35893f62 --- /dev/null +++ b/course/code/17/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/17/import_vcpkg/build.zig b/course/code/17/import_vcpkg/build.zig new file mode 100644 index 00000000..3c8979e2 --- /dev/null +++ b/course/code/17/import_vcpkg/build.zig @@ -0,0 +1,52 @@ +const std = @import("std"); + +// 该示例依赖 Windows 下通过 vcpkg 安装的 gsl,仅用于文档展示,默认构建为空操作 +pub fn build(_: *std.Build) void {} + +const Build = struct { + pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // #region translate_c + // 0.17 移除了 @cImport,内置的 addTranslateC 也已弃用, + // 推荐使用官方 translate-c 包把 C 头文件翻译为 Zig 模块。 + // 先执行:zig fetch --save git+https://codeberg.org/ziglang/translate-c#2.0.0 + const Translator = @import("translate_c").Translator; + const translate_c = b.dependency("translate_c", .{}); + + const gsl: Translator = .init(translate_c, .{ + // src/gsl.h 中只有一行 #include + .c_source_file = b.path("src/gsl.h"), + .target = target, + .optimize = optimize, + }); + // 翻译阶段同样需要能找到 gsl 的头文件 + gsl.addIncludePath(.{ .cwd_relative = "D:\\vcpkg\\installed\\windows-x64\\include" }); + // #endregion translate_c + + const exe = b.addExecutable(.{ + .name = "c_lib_import_gsl_windows-x64", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + // 把翻译结果作为名为 gsl 的模块导入 + .imports = &.{ + .{ .name = "gsl", .module = gsl.mod }, + }, + }), + }); + + // #region c_import + // 增加 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/17/import_vcpkg/build.zig.zon b/course/code/17/import_vcpkg/build.zig.zon new file mode 100644 index 00000000..ec0454bf --- /dev/null +++ b/course/code/17/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/17/import_vcpkg/src/gsl.h b/course/code/17/import_vcpkg/src/gsl.h new file mode 100644 index 00000000..1cf2513f --- /dev/null +++ b/course/code/17/import_vcpkg/src/gsl.h @@ -0,0 +1 @@ +#include diff --git a/course/code/17/import_vcpkg/src/main.zig b/course/code/17/import_vcpkg/src/main.zig new file mode 100644 index 00000000..6dcb0563 --- /dev/null +++ b/course/code/17/import_vcpkg/src/main.zig @@ -0,0 +1,28 @@ +const std = @import("std"); + +// #region import_gsl +// 0.17 移除了 @cImport,gsl 头文件在 build.zig 中由 translate-c 翻译为模块 +const gsl = @import("gsl"); +// #endregion import_gsl + +pub fn main(init: std.process.Init) !void { + const n = 8; + + const allocator = init.arena.allocator(); + + // #region use_gsl_fft + // [实数0,虚数0,实数1,虚数1,实数2,虚数2,...] + var data: []f64 = try allocator.alloc(f64, n * 2); + @memset(data, 0); + // 虚数恒为0,实数为0,1,2,... + for (0..n) |i| data[i * 2] = @floatFromInt(i); + // 快速离散傅里叶变换 + _ = gsl.gsl_fft_complex_radix2_forward(data.ptr, 1, n); + // 输出结果 + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(init.io, &stdout_buffer); + const stdout = &stdout_writer.interface; + try stdout.print("\n{any}\n", .{data}); + try stdout.flush(); + // #endregion use_gsl_fft +} diff --git a/course/code/17/interact_with_c.zig b/course/code/17/interact_with_c.zig new file mode 100644 index 00000000..7f32bc7b --- /dev/null +++ b/course/code/17/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/17/loop.zig b/course/code/17/loop.zig new file mode 100644 index 00000000..0a022073 --- /dev/null +++ b/course/code/17/loop.zig @@ -0,0 +1,323 @@ +pub fn main() !void { + ForArray.main(); + ForHandleArray.main(); + IndexFor.main(); + MultiFor.main(); + ForAsExpression.main(); + LabelFor.main(); + try InlineFor.main(); + // WhileBasic 演示的是在 i == 5 时会陷入死循环的写法(文档中会讲解原因),这里不调用它 + _ = 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 { + const std = @import("std"); + + pub fn main() void { + { + // #region while_continue_fix + // 将while语句的基础代码用continue表达式改写 + var i: usize = 0; + while (i < 10) : (i += 1) { + if (i == 5) continue; + std.debug.print("i is {}\n", .{i}); + } + // #endregion while_continue_fix + } + + { + // #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/17/memory_manager.zig b/course/code/17/memory_manager.zig new file mode 100644 index 00000000..3dcf9975 --- /dev/null +++ b/course/code/17/memory_manager.zig @@ -0,0 +1,240 @@ +pub fn main() !void { + try SafeAllocator.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 BufferFirstAllocator.main(); + try MemoryPool.main(); +} + +const SafeAllocator = struct { + // #region SafeAllocator + const std = @import("std"); + + pub fn main() !void { + // 0.17 使用 SafeAllocator 取代了 DebugAllocator + // 它需要一个后备分配器(backing allocator),一定要是变量,不能是常量 + var safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + // 拿到一个allocator + const allocator = safe.allocator(); + + // defer 用于执行 SafeAllocator 善后工作 + defer { + // deinit 会报告并释放所有泄漏的内存,返回值为泄漏的数量 + const leaks = safe.deinit(); + + // 检测是否发生内存泄漏 + if (leaks != 0) @panic("TEST FAIL"); + } + + //申请内存 + const bytes = try allocator.alloc(u8, 100); + // 延后释放内存 + defer allocator.free(bytes); + } + // #endregion SafeAllocator +}; + +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 + // 通用的 ThreadSafeAllocator 包装器已被移除, + // FixedBufferAllocator 自身提供了线程安全的分配器接口 + // 注意:不要同时混用 allocator() 和 threadSafeAllocator() 返回的接口 + const allocator = fba.threadSafeAllocator(); + + // 申请内存 + 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 safe_allocator: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + + pub fn main() !void { + const allocator, const is_debug = allocator: { + if (builtin.target.os.tag == .wasi) break :allocator .{ std.heap.wasm_allocator, false }; + // 0.17 中优化模式的标签改为 .debug、.safe、.fast、.small + break :allocator switch (builtin.mode) { + .debug, .safe => .{ safe_allocator.allocator(), true }, + .fast, .small => .{ std.heap.smp_allocator, false }, + }; + }; + defer if (is_debug) { + _ = safe_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 safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + // 拿到一个allocator + const allocator = safe.allocator(); + + // defer 用于执行 SafeAllocator 善后工作 + defer { + if (safe.deinit() != 0) @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 BufferFirstAllocator = struct { + // #region buffer_first_allocator + const std = @import("std"); + + pub fn main() !void { + // 0.17 中 stackFallback 被重做为 BufferFirstAllocator,缓冲区改为由调用者传入 + // 先在栈上准备 256 个字节的缓冲区 + var buffer: [256]u8 = undefined; + // 优先从缓冲区分配,如果缓冲区不够用,就会使用 page allocator + var bfa: std.heap.BufferFirstAllocator = .init(&buffer, std.heap.page_allocator); + // 获取分配器,和其他分配器一样调用 allocator() + const allocator = bfa.allocator(); + // 申请内存 + const memory = try allocator.alloc(u8, 100); + // 释放内存 + defer allocator.free(memory); + } + // #endregion buffer_first_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 +}; + +test "allocators" { + try SafeAllocator.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 BufferFirstAllocator.main(); + try MemoryPool.main(); +} diff --git a/course/code/17/number.zig b/course/code/17/number.zig new file mode 100644 index 00000000..59241a30 --- /dev/null +++ b/course/code/17/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/17/opaque.zig b/course/code/17/opaque.zig new file mode 100644 index 00000000..01b9eb58 --- /dev/null +++ b/course/code/17/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/17/optional_type.zig b/course/code/17/optional_type.zig new file mode 100644 index 00000000..ab242483 --- /dev/null +++ b/course/code/17/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/17/package_management_exporter/Readme.md b/course/code/17/package_management_exporter/Readme.md new file mode 100644 index 00000000..44e8e91d --- /dev/null +++ b/course/code/17/package_management_exporter/Readme.md @@ -0,0 +1 @@ +该目录仅仅是作为演示用的空项目! diff --git a/course/code/17/package_management_exporter/build.zig b/course/code/17/package_management_exporter/build.zig new file mode 100644 index 00000000..ac1784a0 --- /dev/null +++ b/course/code/17/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/17/package_management_exporter/build.zig.zon b/course/code/17/package_management_exporter/build.zig.zon new file mode 100644 index 00000000..3f2fcacd --- /dev/null +++ b/course/code/17/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/17/package_management_exporter/src/root.zig b/course/code/17/package_management_exporter/src/root.zig new file mode 100644 index 00000000..19ab525b --- /dev/null +++ b/course/code/17/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/17/package_management_importer/Readme.md b/course/code/17/package_management_importer/Readme.md new file mode 100644 index 00000000..44e8e91d --- /dev/null +++ b/course/code/17/package_management_importer/Readme.md @@ -0,0 +1 @@ +该目录仅仅是作为演示用的空项目! diff --git a/course/code/17/package_management_importer/build.zig b/course/code/17/package_management_importer/build.zig new file mode 100644 index 00000000..62723df4 --- /dev/null +++ b/course/code/17/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/17/package_management_importer/build.zig.zon b/course/code/17/package_management_importer/build.zig.zon new file mode 100644 index 00000000..bedb54b3 --- /dev/null +++ b/course/code/17/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/refs/tags/0.0.17.tar.gz", + .hash = "zig_msgpack-0.0.14-evvueL5SBQACmim6j6klQ9wWIIG_UxGlPvVYdiNy0KT8", + }, + .@"path-exporter" = .{ + // path 为本地包的路径 + .path = "../package_management_exporter", + }, + }, + // 包所包含的源文件,一般用于在对外提供包时才使用,还是建议养成写清楚paths的习惯 + .paths = .{ + "src", + "build.zig", + "build.zig.zon", + }, +} +// #endregion package_management diff --git a/course/code/17/package_management_importer/src/main.zig b/course/code/17/package_management_importer/src/main.zig new file mode 100644 index 00000000..5c0df21e --- /dev/null +++ b/course/code/17/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/17/pointer.zig b/course/code/17/pointer.zig new file mode 100644 index 00000000..ce5c61ea --- /dev/null +++ b/course/code/17/pointer.zig @@ -0,0 +1,290 @@ +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(); + try ptrCast.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); + // 0.16 起,即便对齐值相同,显式写出 align 的指针类型与省略 align 的指针类型 + // 也不再是同一个类型,但二者可以相互隐式转换 + try expect(*i32 != *align(align_of_i32) i32); + const aligned_ptr: *align(align_of_i32) i32 = &x; + const natural_ptr: *i32 = aligned_ptr; + try expect(natural_ptr.* == 1234); + // 0.17 起指针属性统一放在 attrs 中,未显式指定对齐时 attrs.@"align" 为 null + try expect(@typeInfo(*i32).pointer.attrs.@"align" == null); + try expect(@typeInfo(*align(8) i32).pointer.attrs.@"align" == 8); + + if (builtin.target.cpu.arch == .x86_64) { + // 获取了 x86_64 架构下 i32 的对齐大小 + try expect(@alignOf(i32) == 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.attrs.@"align" == 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", .{}); + } + + // 通过内置函数转换 + // 0.17 起 @bitCast 与目标端序无关:数组第一个元素对应结果的最低有效位 + if (@as(u32, @bitCast(bytes)) == 0x12121212) { + std.debug.print("success\n", .{}); + } + // #endregion ptr_cast + } +}; + +test "pointer" { + try MultiPointer.main(); + try ArrayAndSlice.main(); + try Volatile.main(); + try Align.main(); + try AlignCast.main(); + try ZeroPointer.main(); + ComptimePointer.main(); + try compPointer.main(); + try ptrCast.main(); + + const bytes = [4]u8{ 0x01, 0x02, 0x03, 0x04 }; + try @import("std").testing.expectEqual(0x04030201, @as(u32, @bitCast(bytes))); +} diff --git a/course/code/17/reflection.zig b/course/code/17/reflection.zig new file mode 100644 index 00000000..b451a4f5 --- /dev/null +++ b/course/code/17/reflection.zig @@ -0,0 +1,290 @@ +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"; + + // 0.17 起类型信息采用“数组结构体”(Struct-Of-Arrays)风格: + // 字段名、字段类型、字段属性分别存放在 field_names、field_types、field_attrs 中 + // inline for 同时遍历字段名与字段类型 + inline for (struct_info.field_names, struct_info.field_types) |field_name, field_type| { + 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"; + const fields_len = struct_info.field_names.len; + + // 0.17 的类型信息与 @Struct 的参数形式一致,字段名和字段类型可以直接复用, + // 这里只需要准备新的字段属性 + comptime var field_attrs: [fields_len]std.lang.Type.Struct.FieldAttributes = undefined; + inline for (&field_attrs, struct_info.field_attrs) |*new_attrs, old_attrs| { + // 保留原有属性(例如默认值),仅把对齐改为 1 + new_attrs.* = old_attrs; + new_attrs.@"align" = 1; + } + + // 使用 @Struct 构造新类型(extern 布局,对齐为 1) + return @Struct( + .@"extern", + null, + struct_info.field_names, + struct_info.field_types[0..fields_len], + &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")}); + // false + // 0.17 起 @hasDecl 只对 pub 声明返回 true,即便类型和代码处于同一个文件中也是如此 + 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 +}; + +test "typeInfo field names" { + const std = @import("std"); + const info = @typeInfo(typeInfo.T).@"struct"; + try std.testing.expectEqual(2, info.field_names.len); + try std.testing.expectEqualStrings("a", info.field_names[0]); + try std.testing.expectEqual(u8, info.field_types[1]); +} + +test "hasDecl" { + const std = @import("std"); + try std.testing.expect(@hasDecl(hasDecl.Foo, "blah")); + try std.testing.expect(!@hasDecl(hasDecl.Foo, "hi")); + try std.testing.expect(!@hasDecl(hasDecl.Foo, "nope")); + try std.testing.expect(!@hasDecl(hasDecl.Foo, "nope1234")); +} diff --git a/course/code/17/result-location.zig b/course/code/17/result-location.zig new file mode 100644 index 00000000..c8cf3851 --- /dev/null +++ b/course/code/17/result-location.zig @@ -0,0 +1,391 @@ +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 StdLibSafeAllocator.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.ArrayList(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.ArrayList(i32) = .empty, + }; + + test "ArrayList 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 StdLibSafeAllocator = struct { + // #region stdlib_safe_allocator + pub fn main() !void { + // 0.17 中 SafeAllocator 取代了 DebugAllocator, + // 它的 init 是一个函数,同样可以通过声明字面量 .init(...) 调用 + var safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + defer _ = safe.deinit(); + + const allocator = safe.allocator(); + const ptr = try allocator.alloc(u8, 100); + defer allocator.free(ptr); + + std.debug.print("allocated {} bytes\n", .{ptr.len}); + } + + test "safe allocator with decl literal" { + var safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + defer _ = safe.deinit(); + + const allocator = safe.allocator(); + const ptr = try allocator.alloc(u8, 100); + defer allocator.free(ptr); + + try std.testing.expectEqual(100, ptr.len); + } + // #endregion stdlib_safe_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 safe allocator" { + var safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + defer _ = safe.deinit(); + const allocator = safe.allocator(); + const ptr = try allocator.alloc(u8, 100); + defer allocator.free(ptr); + try std.testing.expectEqual(100, ptr.len); +} diff --git a/course/code/17/slice.zig b/course/code/17/slice.zig new file mode 100644 index 00000000..ff75aa21 --- /dev/null +++ b/course/code/17/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/17/string.zig b/course/code/17/string.zig new file mode 100644 index 00000000..ad347839 --- /dev/null +++ b/course/code/17/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/17/struct.zig b/course/code/17/struct.zig new file mode 100644 index 00000000..b5b4ad88 --- /dev/null +++ b/course/code/17/struct.zig @@ -0,0 +1,501 @@ +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"); + + // 0.17 使用 SafeAllocator 取代了 DebugAllocator + var safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + + // #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 = safe.allocator(); + defer { + // deinit 返回泄漏的内存块数量 + const leaks = safe.deinit(); + if (leaks != 0) 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); + + // 0.17 起 @bitCast 只关心“逻辑位”,与目标架构的端序无关: + // 数组的第一个元素对应最低有效位,因此在任何架构上结果都一样 + const ordered: [2]u8 = @bitCast(full); + try expect(ordered[0] == 0x34); + try expect(ordered[1] == 0x12); + + // 如果需要观察内存中真实的字节排列(与端序相关),可以使用 std.mem.toBytes + const in_memory = std.mem.toBytes(full); + switch (native_endian) { + .big => { + try expect(in_memory[0] == 0x12); + try expect(in_memory[1] == 0x34); + }, + .little => { + try expect(in_memory[0] == 0x34); + try expect(in_memory[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 +}; + +test "packed cast" { + try PackedCast.main(); +} diff --git a/course/code/17/switch.zig b/course/code/17/switch.zig new file mode 100644 index 00000000..b23f9f28 --- /dev/null +++ b/course/code/17/switch.zig @@ -0,0 +1,317 @@ +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 { + // 0.17 起结构体的字段类型单独存放在 field_types 中 + const field_types = @typeInfo(T).@"struct".field_types; + return switch (field_index) { + // 这里每次都是不同的值 + inline 0...field_types.len - 1 => |idx| { + return @typeInfo(field_types[idx]) == .optional; + }, + else => return error.IndexOutOfBounds, + }; +} +// #endregion isFieldOptional + +// #region withSwitch +const AnySlice = union(enum) { + a: []const u8, + b: []const i8, + c: []const bool, + d: []const u32, +}; + +fn withSwitch(any: AnySlice) usize { + return switch (any) { + // 这里的 slice 可以匹配所有的 Anyslice 类型 + inline else => |slice| slice.len, + }; +} +// #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 @intFromFloat(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"); + // std.BoundedArray 已被移除,这里使用基于固定缓冲区的 ArrayList 代替 + var buffer: [8]i32 = undefined; + var stack: std.ArrayList(i32) = .initBuffer(&buffer); + try stack.appendSliceBounded(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.appendBounded(stack.pop().? + stack.pop().?); + + ip += 1; + continue :vm code[ip]; + }, + .mul => { + try stack.appendBounded(stack.pop().? * stack.pop().?); + + ip += 1; + continue :vm code[ip]; + }, + .end => stack.pop().?, + }; +} +// #endregion vm + +test "isFieldOptional" { + const std = @import("std"); + const S = struct { a: u8, b: ?u8 }; + try std.testing.expect(!try isFieldOptional(S, 0)); + try std.testing.expect(try isFieldOptional(S, 1)); + try std.testing.expectError(error.IndexOutOfBounds, isFieldOptional(S, 2)); +} + +test "withSwitch" { + const std = @import("std"); + try std.testing.expectEqual(3, withSwitch(.{ .a = "abc" })); + try std.testing.expectEqual(2, withSwitch(.{ .c = &.{ true, false } })); +} + +test "getNum" { + const std = @import("std"); + try std.testing.expectEqual(42, getNum(.{ .a = 42 })); + try std.testing.expectEqual(3, getNum(.{ .b = 3.7 })); +} + +test "vm" { + const std = @import("std"); + // 栈顶在右侧:先计算 3 + 2 = 5,再计算 7 * 5 = 35 + try std.testing.expectEqual(35, try evaluate(&.{ 7, 2, 3 }, &.{ .add, .mul, .end })); +} + +test "switch main" { + try main(); +} diff --git a/course/code/17/type-cast.zig b/course/code/17/type-cast.zig new file mode 100644 index 00000000..a400d10d --- /dev/null +++ b/course/code/17/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/17/union.zig b/course/code/17/union.zig new file mode 100644 index 00000000..be251e64 --- /dev/null +++ b/course/code/17/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/17/unit_test.zig b/course/code/17/unit_test.zig new file mode 100644 index 00000000..2f619a3d --- /dev/null +++ b/course/code/17/unit_test.zig @@ -0,0 +1,88 @@ +pub fn main() !void {} + +// #region Basic +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; + } +}; +// #endregion Basic + +// #region Nestd +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); + } + }; +}; +// #endregion Nestd + +test "all" { + _ = Basic; +} + +// #region allDecl +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); + } + } +}; +// #endregion allDecl diff --git a/course/code/17/unreachable.zig b/course/code/17/unreachable.zig new file mode 100644 index 00000000..95ccf4e4 --- /dev/null +++ b/course/code/17/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/17/vector.zig b/course/code/17/vector.zig new file mode 100644 index 00000000..993ab895 --- /dev/null +++ b/course/code/17/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/code/release/array.zig b/course/code/release/array.zig index 227f1a60..6c997674 100644 --- a/course/code/release/array.zig +++ b/course/code/release/array.zig @@ -82,8 +82,14 @@ const Multiply = struct { const print = @import("std").debug.print; pub fn main() void { + // 0.17 移除了数组乘法语法 `**` + // 用同一个值填充整个数组,使用 @splat + const zeros: [5]i8 = @splat(0); + print("{any}\n", .{zeros}); // [5]i8{ 0, 0, 0, 0, 0 } + + // 重复一个数组,可以在编译期用 ++ 串联 const small = [3]i8{ 1, 2, 3 }; - const big: [9]i8 = small ** 3; + const big: [9]i8 = small ++ small ++ small; print("{any}\n", .{big}); // [9]i8{ 1, 2, 3, 1, 2, 3, 1, 2, 3 } } // #endregion multiply @@ -108,7 +114,8 @@ const FuncInitArray = struct { const print = @import("std").debug.print; pub fn main() void { - const array = [_]i32{make(3)} ** 10; + // 0.17 起使用 @splat 代替 `[_]i32{make(3)} ** 10` + const array: [10]i32 = @splat(make(3)); print("{any}\n", .{array}); } @@ -136,3 +143,19 @@ const ComptimeInitArray = struct { } // #endregion comptime_init_array }; + +test "multiply" { + const std = @import("std"); + const zeros: [5]i8 = @splat(0); + try std.testing.expectEqualSlices(i8, &.{ 0, 0, 0, 0, 0 }, &zeros); + + const small = [3]i8{ 1, 2, 3 }; + const big: [9]i8 = small ++ small ++ small; + try std.testing.expectEqualSlices(i8, &.{ 1, 2, 3, 1, 2, 3, 1, 2, 3 }, &big); +} + +test "func init array" { + const std = @import("std"); + const array: [10]i32 = @splat(FuncInitArray.make(3)); + for (array) |item| try std.testing.expectEqual(4, item); +} diff --git a/course/code/release/build_system/basic/src/main.zig b/course/code/release/build_system/basic/src/main.zig index bf45938f..a58cdbd6 100644 --- a/course/code/release/build_system/basic/src/main.zig +++ b/course/code/release/build_system/basic/src/main.zig @@ -18,8 +18,9 @@ pub fn main(init: std.process.Init) !void { } 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); + const gpa = std.testing.allocator; + var list: std.ArrayList(i32) = .empty; + defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak! + try list.append(gpa, 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 index 2cc7cd76..481735ec 100644 --- a/course/code/release/build_system/build.zig +++ b/course/code/release/build_system/build.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const args = [_][]const u8{ "zig", "build" }; pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); @@ -35,54 +34,34 @@ pub fn build(b: *std.Build) !void { b.installArtifact(exe); - const full_path = try std.process.currentPathAlloc(io, b.allocator); - defer b.allocator.free(full_path); + // 0.17 起 configure 阶段的结果会被缓存,遍历目录前需要声明对目录内容的依赖 + b.dependOnDirectoryContents(b.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); - }; + // `b.root` 是当前构建根目录(Cache.Path),取代了旧的 `b.build_root` + var dir = try b.root.openDir(io, ".", .{ .iterate = true }); defer dir.close(io); var iterate = dir.iterate(); + while (try iterate.next(io)) |entry| { + if (entry.kind != .directory) continue; + if (entry.name[0] == '.' or std.mem.eql(u8, entry.name, "zig-out")) continue; - 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); + // 0.17 将 configure 与 make 拆成了两个进程,不能再在 build 函数中直接 spawn 子进程, + // 而是把子项目的 `zig build` 声明为 Run 步骤,交给 make 阶段执行 + const sub_build = b.addSystemCommand(&.{ b.graph.zig_exe, "build" }); + sub_build.setName(b.fmt("zig build ({s})", .{entry.name})); + sub_build.setCwd(b.path(entry.name)); + sub_build.stdio = .inherit; + b.getInstallStep().dependOn(&sub_build.step); - 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; + // 演示单元测试的子项目额外执行一次 `zig build test`,确保示例中的测试代码同样能通过 + if (std.mem.eql(u8, entry.name, "test")) { + const sub_test = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "test" }); + sub_test.setName(b.fmt("zig build test ({s})", .{entry.name})); + sub_test.setCwd(b.path(entry.name)); + sub_test.stdio = .inherit; + sub_test.step.dependOn(&sub_build.step); + b.getInstallStep().dependOn(&sub_test.step); } } } - -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/src/main.zig b/course/code/release/build_system/cli/src/main.zig index 947ed515..430559b8 100644 --- a/course/code/release/build_system/cli/src/main.zig +++ b/course/code/release/build_system/cli/src/main.zig @@ -18,8 +18,9 @@ pub fn main(init: std.process.Init) !void { } 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); + const gpa = std.testing.allocator; + var list: std.ArrayList(i32) = .empty; + defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak! + try list.append(gpa, 42); try std.testing.expectEqual(@as(i32, 42), list.pop()); } diff --git a/course/code/release/build_system/embedfile/build.zig b/course/code/release/build_system/embedfile/build.zig index 44ba9a52..9bb665ec 100644 --- a/course/code/release/build_system/embedfile/build.zig +++ b/course/code/release/build_system/embedfile/build.zig @@ -32,9 +32,8 @@ pub fn build(b: *std.Build) void { run_cmd.step.dependOn(b.getInstallStep()); // 传递参数 - if (b.args) |args| { - run_cmd.addArgs(args); - } + // 0.17 移除了 b.args,改为声明一个“透传参数”占位,make 阶段会替换为 `--` 之后的参数 + run_cmd.addPassthruArgs(); // 指定一个 step 为 run const run_step = b.step("run", "Run the app"); diff --git a/course/code/release/build_system/externalfile/build.zig b/course/code/release/build_system/externalfile/build.zig index e1c6dcd9..2a93af92 100644 --- a/course/code/release/build_system/externalfile/build.zig +++ b/course/code/release/build_system/externalfile/build.zig @@ -51,9 +51,8 @@ pub fn build(b: *std.Build) !void { run_cmd.step.dependOn(b.getInstallStep()); // 传递参数 - if (b.args) |args| { - run_cmd.addArgs(args); - } + // 0.17 移除了 b.args,改为声明一个“透传参数”占位,make 阶段会替换为 `--` 之后的参数 + run_cmd.addPassthruArgs(); // 指定一个 step 为 run const run_step = b.step("run", "Run the app"); diff --git a/course/code/release/build_system/step/build.zig b/course/code/release/build_system/step/build.zig index 6e40f3b4..abce0953 100644 --- a/course/code/release/build_system/step/build.zig +++ b/course/code/release/build_system/step/build.zig @@ -32,9 +32,8 @@ pub fn build(b: *std.Build) void { // 注意:此步骤可选 // 此操作允许用户通过构建系统的命令传递参数,例如 zig build -- arg1 arg2 // 当前是将参数传递给运行构建结果 - if (b.args) |args| { - run_exe.addArgs(args); - } + // 0.17 移除了 b.args,改为声明一个“透传参数”占位,make 阶段会替换为 `--` 之后的参数 + run_exe.addPassthruArgs(); // 指定一个 step 为 run const run_step = b.step("run", "Run the application"); diff --git a/course/code/release/build_system/test/src/main.zig b/course/code/release/build_system/test/src/main.zig index bf45938f..a58cdbd6 100644 --- a/course/code/release/build_system/test/src/main.zig +++ b/course/code/release/build_system/test/src/main.zig @@ -18,8 +18,9 @@ pub fn main(init: std.process.Init) !void { } 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); + const gpa = std.testing.allocator; + var list: std.ArrayList(i32) = .empty; + defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak! + try list.append(gpa, 42); try std.testing.expectEqual(@as(i32, 42), list.pop()); } diff --git a/course/code/release/build_system/tinytetris/build.zig b/course/code/release/build_system/tinytetris/build.zig index dd977698..3628a12d 100644 --- a/course/code/release/build_system/tinytetris/build.zig +++ b/course/code/release/build_system/tinytetris/build.zig @@ -48,9 +48,8 @@ pub fn build(b: *std.Build) void { run_cmd.step.dependOn(b.getInstallStep()); // 运行时参数传递 - if (b.args) |args| { - run_cmd.addArgs(args); - } + // 0.17 移除了 b.args,改为声明一个“透传参数”占位,make 阶段会替换为 `--` 之后的参数 + run_cmd.addPassthruArgs(); // 运行的 step const run_step = b.step("run", "Run the app"); diff --git a/course/code/release/enum.zig b/course/code/release/enum.zig index ab471b21..69e8647d 100644 --- a/course/code/release/enum.zig +++ b/course/code/release/enum.zig @@ -1,6 +1,7 @@ pub fn main() !void { try EnumSize.main(); try EnumReference.main(); + try Non_exhaustiveEnum.main(); } // #region basic_enum @@ -67,9 +68,12 @@ const EnumSize = struct { }; 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")); + const info = @typeInfo(Small).@"enum"; + try expect(info.tag_type == u2); + // 0.17 起类型信息采用“数组结构体”风格:字段名与字段值分别存放 + try expect(info.field_names.len == 4); + try expect(mem.eql(u8, info.field_names[1], "two")); + try expect(info.field_values[1] == 1); try expect(mem.eql(u8, @tagName(Small.three), "three")); } // #endregion enum_size @@ -128,16 +132,19 @@ const Non_exhaustiveEnum = struct { }; // 明确列出的枚举值 - const blue: Color = @enumFromInt(2); + // 0.17 使用 @fromBackingInt 代替 @enumFromInt + const blue: Color = @fromBackingInt(2); try expect(blue == .blue); // 未列出的枚举值:8 在 u4 的范围内(0~15) - const yellow: Color = @enumFromInt(8); + const yellow: Color = @fromBackingInt(8); try expect(@TypeOf(yellow) == Color); - try expect(@intFromEnum(yellow) == 8); + // 0.17 使用 @backingInt 代替 @intFromEnum,结果类型就是标记类型 u4 + try expect(@backingInt(yellow) == 8); + try expect(@TypeOf(@backingInt(yellow)) == u4); - // 42 超出了 u4 的范围,会触发未定义行为 - // const ub: Color = @enumFromInt(42); + // @fromBackingInt 的参数必须恰好是标记类型 u4,42 超出了 u4 的范围,无法通过编译 + // const ub: Color = @fromBackingInt(42); // #endregion enum_from_int } @@ -148,7 +155,7 @@ const EnumLiteral_ = struct { pub fn main() !void { // #region enum_literal // 使用内建函数 @EnumLiteral 构造出一个 EnumLiteral 类型 - // Zig 0.16 使用 @EnumLiteral() 替代 @Type(.enum_literal) + // Zig 0.16 起使用 @EnumLiteral() 替代 @Type(.enum_literal) const EnumLiteralType: type = @EnumLiteral(); // 定义一个常量 enum_literal,它的类型为 EnumLiteral,并赋值为 ".kkk" @@ -159,3 +166,9 @@ const EnumLiteral_ = struct { // #endregion enum_literal } }; + +test "enum" { + try EnumSize.main(); + try EnumReference.main(); + try Non_exhaustiveEnum.main(); +} diff --git a/course/code/release/error_handle.zig b/course/code/release/error_handle.zig index c65439e9..b6f2b4b0 100644 --- a/course/code/release/error_handle.zig +++ b/course/code/release/error_handle.zig @@ -183,17 +183,28 @@ const ErrDefer = struct { const std = @import("std"); // #region DeferErrorCapture + // 0.17 移除了 `errdefer |err| { ... }` 捕获语法 + // 需要观察错误时,把函数拆成两层,在外层用 catch 捕获 fn deferErrorCaptureExample() !void { - // 捕获错误 - errdefer |err| { + deferErrorCaptureInner() catch |err| { std.debug.print("the error is {s}\n", .{@errorName(err)}); - } + return err; + }; + } + + fn deferErrorCaptureInner() !void { + // 这里依然可以使用不带捕获的 errdefer 做清理 + errdefer std.debug.print("cleanup before returning error\n", .{}); return error.DeferError; } // #endregion DeferErrorCapture }; +test "errdefer capture migration" { + try @import("std").testing.expectError(error.DeferError, ErrDefer.deferErrorCaptureExample()); +} + const DeferErrDefer = struct { // #region DeferErrDefer const std = @import("std"); diff --git a/course/code/release/import_dependency_build/build.zig b/course/code/release/import_dependency_build/build.zig index c646ed7f..f8303ac5 100644 --- a/course/code/release/import_dependency_build/build.zig +++ b/course/code/release/import_dependency_build/build.zig @@ -1,56 +1,33 @@ 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); - }; + // 0.17 起 configure 阶段的结果会被缓存,遍历目录前需要声明对目录内容的依赖 + b.dependOnDirectoryContents(b.path(".")); + + // `b.root` 是当前构建根目录(Cache.Path),不依赖命令执行时所在的目录 + var dir = try b.root.openDir(io, ".", .{ .iterate = true }); 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; - } + while (try iterate.next(io)) |entry| { + if (entry.kind != .directory) continue; + if (entry.name[0] == '.' or std.mem.eql(u8, entry.name, "zig-out")) continue; + + // 每个子目录都应当是一个带 build.zig 的包 + var entry_dir = try dir.openDir(io, entry.name, .{}); + defer entry_dir.close(io); + entry_dir.access(io, "build.zig", .{}) catch { + std.debug.panic("not found build.zig in {s}", .{entry.name}); + }; + + // 0.17 将 configure 与 make 拆成了两个进程,不能再在 build 函数中直接 spawn 子进程, + // 而是把子项目的 `zig build` 声明为 Run 步骤,并使用当前正在运行的 zig,交给 make 阶段执行 + const sub_build = b.addSystemCommand(&.{ b.graph.zig_exe, "build" }); + sub_build.setName(b.fmt("zig build ({s})", .{entry.name})); + sub_build.setCwd(b.path(entry.name)); + sub_build.stdio = .inherit; + b.getInstallStep().dependOn(&sub_build.step); } } - -fn eqlu8(a: []const u8, b: []const u8) bool { - return std.mem.eql(u8, a, b); -} diff --git a/course/code/release/import_vcpkg/build.zig b/course/code/release/import_vcpkg/build.zig index 7475753a..3c8979e2 100644 --- a/course/code/release/import_vcpkg/build.zig +++ b/course/code/release/import_vcpkg/build.zig @@ -1,4 +1,6 @@ const std = @import("std"); + +// 该示例依赖 Windows 下通过 vcpkg 安装的 gsl,仅用于文档展示,默认构建为空操作 pub fn build(_: *std.Build) void {} const Build = struct { @@ -6,17 +8,37 @@ const Build = struct { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + // #region translate_c + // 0.17 移除了 @cImport,内置的 addTranslateC 也已弃用, + // 推荐使用官方 translate-c 包把 C 头文件翻译为 Zig 模块。 + // 先执行:zig fetch --save git+https://codeberg.org/ziglang/translate-c#2.0.0 + const Translator = @import("translate_c").Translator; + const translate_c = b.dependency("translate_c", .{}); + + const gsl: Translator = .init(translate_c, .{ + // src/gsl.h 中只有一行 #include + .c_source_file = b.path("src/gsl.h"), + .target = target, + .optimize = optimize, + }); + // 翻译阶段同样需要能找到 gsl 的头文件 + gsl.addIncludePath(.{ .cwd_relative = "D:\\vcpkg\\installed\\windows-x64\\include" }); + // #endregion translate_c + const exe = b.addExecutable(.{ .name = "c_lib_import_gsl_windows-x64", - .root_module = b.addModule("c_lib_import_gsl_windows-x64", .{ + .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, + // 把翻译结果作为名为 gsl 的模块导入 + .imports = &.{ + .{ .name = "gsl", .module = gsl.mod }, + }, }), }); + // #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库 diff --git a/course/code/release/import_vcpkg/src/gsl.h b/course/code/release/import_vcpkg/src/gsl.h new file mode 100644 index 00000000..1cf2513f --- /dev/null +++ b/course/code/release/import_vcpkg/src/gsl.h @@ -0,0 +1 @@ +#include diff --git a/course/code/release/import_vcpkg/src/main.zig b/course/code/release/import_vcpkg/src/main.zig index 533f922b..6dcb0563 100644 --- a/course/code/release/import_vcpkg/src/main.zig +++ b/course/code/release/import_vcpkg/src/main.zig @@ -1,25 +1,28 @@ const std = @import("std"); // #region import_gsl -const gsl = @cImport({ - @cInclude("gsl/gsl_fft_complex.h"); -}); +// 0.17 移除了 @cImport,gsl 头文件在 build.zig 中由 translate-c 翻译为模块 +const gsl = @import("gsl"); // #endregion import_gsl -pub fn main() !void { +pub fn main(init: std.process.Init) !void { const n = 8; - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const allocator = arena.allocator(); + const allocator = init.arena.allocator(); + // #region use_gsl_fft // [实数0,虚数0,实数1,虚数1,实数2,虚数2,...] var data: []f64 = try allocator.alloc(f64, n * 2); + @memset(data, 0); // 虚数恒为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}); + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(init.io, &stdout_buffer); + const stdout = &stdout_writer.interface; + try stdout.print("\n{any}\n", .{data}); + try stdout.flush(); // #endregion use_gsl_fft } diff --git a/course/code/release/interact_with_c.zig b/course/code/release/interact_with_c.zig index 7ae51e88..7f32bc7b 100644 --- a/course/code/release/interact_with_c.zig +++ b/course/code/release/interact_with_c.zig @@ -30,7 +30,7 @@ const external = struct { // #region external // 使用 callconv 声明函数调用约定为 C - fn add(count: c_int, ...) callconv(.C) c_int { + fn add(count: c_int, ...) callconv(.c) c_int { // 对应 C 的宏 va_start var ap = @cVaStart(); // 对应 C 的宏 va_end diff --git a/course/code/release/loop.zig b/course/code/release/loop.zig index ab2b194a..0a022073 100644 --- a/course/code/release/loop.zig +++ b/course/code/release/loop.zig @@ -6,7 +6,8 @@ pub fn main() !void { ForAsExpression.main(); LabelFor.main(); try InlineFor.main(); - WhileBasic.main(); + // WhileBasic 演示的是在 i == 5 时会陷入死循环的写法(文档中会讲解原因),这里不调用它 + _ = WhileBasic.main; WhileContinue.main(); LabelWhile.main(); try InlineWhile.main(); diff --git a/course/code/release/memory_manager.zig b/course/code/release/memory_manager.zig index 52ae7f16..3dcf9975 100644 --- a/course/code/release/memory_manager.zig +++ b/course/code/release/memory_manager.zig @@ -1,5 +1,5 @@ pub fn main() !void { - try DebugAllocator.main(); + try SafeAllocator.main(); try SmpAllocator.main(); try BestAllocator.main(); try FixedBufferAllocator.main(); @@ -7,27 +7,28 @@ pub fn main() !void { try ArenaAllocator.main(); try c_allocator.main(); try page_allocator.main(); - try StackFallbackAllocator.main(); + try BufferFirstAllocator.main(); try MemoryPool.main(); } -const DebugAllocator = struct { - // #region DebugAllocator +const SafeAllocator = struct { + // #region SafeAllocator const std = @import("std"); pub fn main() !void { - // 使用模型,一定要是变量,不能是常量 - var gpa = std.heap.DebugAllocator(.{}){}; + // 0.17 使用 SafeAllocator 取代了 DebugAllocator + // 它需要一个后备分配器(backing allocator),一定要是变量,不能是常量 + var safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); // 拿到一个allocator - const allocator = gpa.allocator(); + const allocator = safe.allocator(); - // defer 用于执行debug_allocator善后工作 + // defer 用于执行 SafeAllocator 善后工作 defer { - // 尝试进行 deinit 操作 - const deinit_status = gpa.deinit(); + // deinit 会报告并释放所有泄漏的内存,返回值为泄漏的数量 + const leaks = safe.deinit(); // 检测是否发生内存泄漏 - if (deinit_status == .leak) @panic("TEST FAIL"); + if (leaks != 0) @panic("TEST FAIL"); } //申请内存 @@ -35,7 +36,7 @@ const DebugAllocator = struct { // 延后释放内存 defer allocator.free(bytes); } - // #endregion DebugAllocator + // #endregion SafeAllocator }; const SmpAllocator = struct { @@ -84,16 +85,10 @@ const ThreadSafeFixedBufferAllocator = struct { 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(); + // 通用的 ThreadSafeAllocator 包装器已被移除, + // FixedBufferAllocator 自身提供了线程安全的分配器接口 + // 注意:不要同时混用 allocator() 和 threadSafeAllocator() 返回的接口 + const allocator = fba.threadSafeAllocator(); // 申请内存 const memory = try allocator.alloc(u8, 100); @@ -106,18 +101,19 @@ const ThreadSafeFixedBufferAllocator = struct { const BestAllocator = struct { const std = @import("std"); const builtin = @import("builtin"); - var debug_allocator: std.heap.DebugAllocator(.{}) = .{}; + var safe_allocator: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); pub fn main() !void { const allocator, const is_debug = allocator: { - if (builtin.os.tag == .wasi) break :allocator .{ std.heap.wasm_allocator, false }; + if (builtin.target.os.tag == .wasi) break :allocator .{ std.heap.wasm_allocator, false }; + // 0.17 中优化模式的标签改为 .debug、.safe、.fast、.small break :allocator switch (builtin.mode) { - .Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true }, - .ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false }, + .debug, .safe => .{ safe_allocator.allocator(), true }, + .fast, .small => .{ std.heap.smp_allocator, false }, }; }; defer if (is_debug) { - _ = debug_allocator.deinit(); + _ = safe_allocator.deinit(); }; //申请内存 const bytes = try allocator.alloc(u8, 100); @@ -132,15 +128,13 @@ const ArenaAllocator = struct { pub fn main() !void { // 使用模型,一定要是变量,不能是常量 - var gpa = std.heap.DebugAllocator(.{}){}; + var safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); // 拿到一个allocator - const allocator = gpa.allocator(); + const allocator = safe.allocator(); - // defer 用于执行 debug allocator 善后工作 + // defer 用于执行 SafeAllocator 善后工作 defer { - const deinit_status = gpa.deinit(); - - if (deinit_status == .leak) @panic("TEST FAIL"); + if (safe.deinit() != 0) @panic("TEST FAIL"); } // 对通用内存分配器进行一层包裹 @@ -184,25 +178,24 @@ const page_allocator = struct { // #endregion page_allocator }; -const StackFallbackAllocator = struct { - // #region stack_fallback_allocator +const BufferFirstAllocator = struct { + // #region buffer_first_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(); + // 0.17 中 stackFallback 被重做为 BufferFirstAllocator,缓冲区改为由调用者传入 + // 先在栈上准备 256 个字节的缓冲区 + var buffer: [256]u8 = undefined; + // 优先从缓冲区分配,如果缓冲区不够用,就会使用 page allocator + var bfa: std.heap.BufferFirstAllocator = .init(&buffer, std.heap.page_allocator); + // 获取分配器,和其他分配器一样调用 allocator() + const allocator = bfa.allocator(); // 申请内存 - const memory = try stack_allocator.alloc(u8, 100); + const memory = try allocator.alloc(u8, 100); // 释放内存 - defer stack_allocator.free(memory); + defer allocator.free(memory); } - // #endregion stack_fallback_allocator + // #endregion buffer_first_allocator }; const MemoryPool = struct { @@ -211,7 +204,7 @@ const MemoryPool = struct { pub fn main() !void { // 此处为了演示,直接使用page allocator - // Zig 0.16 中 MemoryPool 使用 .empty 常量初始化 + // Zig 0.16 起 MemoryPool 使用 .empty 常量初始化 var pool: std.heap.MemoryPool(u32) = .empty; defer pool.deinit(std.heap.page_allocator); @@ -232,3 +225,16 @@ const MemoryPool = struct { } // #endregion MemoryPool }; + +test "allocators" { + try SafeAllocator.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 BufferFirstAllocator.main(); + try MemoryPool.main(); +} diff --git a/course/code/release/opaque.zig b/course/code/release/opaque.zig index ad2313b6..01b9eb58 100644 --- a/course/code/release/opaque.zig +++ b/course/code/release/opaque.zig @@ -3,7 +3,7 @@ const Derp = opaque {}; const Wat = opaque {}; extern fn bar(d: *Derp) void; -fn foo(w: *Wat) callconv(.C) void { +fn foo(w: *Wat) callconv(.c) void { bar(w); } // #endregion opaque diff --git a/course/code/release/pointer.zig b/course/code/release/pointer.zig index 6af33843..ce5c61ea 100644 --- a/course/code/release/pointer.zig +++ b/course/code/release/pointer.zig @@ -11,6 +11,7 @@ pub fn main() !void { ComptimePointer.main(); ptr2int.main(); try compPointer.main(); + try ptrCast.main(); } const SinglePointer = struct { @@ -139,12 +140,19 @@ const Align = struct { const align_of_i32 = @alignOf(@TypeOf(x)); // 尝试比较类型 try expect(@TypeOf(&x) == *i32); - // 尝试在设置内存对齐后再进行类型比较 - try expect(*i32 == *align(align_of_i32) i32); + // 0.16 起,即便对齐值相同,显式写出 align 的指针类型与省略 align 的指针类型 + // 也不再是同一个类型,但二者可以相互隐式转换 + try expect(*i32 != *align(align_of_i32) i32); + const aligned_ptr: *align(align_of_i32) i32 = &x; + const natural_ptr: *i32 = aligned_ptr; + try expect(natural_ptr.* == 1234); + // 0.17 起指针属性统一放在 attrs 中,未显式指定对齐时 attrs.@"align" 为 null + try expect(@typeInfo(*i32).pointer.attrs.@"align" == null); + try expect(@typeInfo(*align(8) i32).pointer.attrs.@"align" == 8); if (builtin.target.cpu.arch == .x86_64) { - // 获取了 x86_64 架构的指针对齐大小 - try expect(@typeInfo(*i32).pointer.alignment == 4); + // 获取了 x86_64 架构下 i32 的对齐大小 + try expect(@alignOf(i32) == 4); } } // #endregion align @@ -167,7 +175,7 @@ const AlignCast = struct { pub fn main() !void { // 全局变量对齐 - try expect(@typeInfo(@TypeOf(&foo)).pointer.alignment == 4); + try expect(@typeInfo(@TypeOf(&foo)).pointer.attrs.@"align" == 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; @@ -258,9 +266,25 @@ const ptrCast = struct { } // 通过内置函数转换 + // 0.17 起 @bitCast 与目标端序无关:数组第一个元素对应结果的最低有效位 if (@as(u32, @bitCast(bytes)) == 0x12121212) { std.debug.print("success\n", .{}); } // #endregion ptr_cast } }; + +test "pointer" { + try MultiPointer.main(); + try ArrayAndSlice.main(); + try Volatile.main(); + try Align.main(); + try AlignCast.main(); + try ZeroPointer.main(); + ComptimePointer.main(); + try compPointer.main(); + try ptrCast.main(); + + const bytes = [4]u8{ 0x01, 0x02, 0x03, 0x04 }; + try @import("std").testing.expectEqual(0x04030201, @as(u32, @bitCast(bytes))); +} diff --git a/course/code/release/reflection.zig b/course/code/release/reflection.zig index 7ededd0e..b451a4f5 100644 --- a/course/code/release/reflection.zig +++ b/course/code/release/reflection.zig @@ -65,11 +65,13 @@ const typeInfo = struct { // 断言它为 struct const struct_info = type_info.@"struct"; - // inline for 打印该结构体内部字段的信息 - inline for (struct_info.fields) |field| { + // 0.17 起类型信息采用“数组结构体”(Struct-Of-Arrays)风格: + // 字段名、字段类型、字段属性分别存放在 field_names、field_types、field_attrs 中 + // inline for 同时遍历字段名与字段类型 + inline for (struct_info.field_names, struct_info.field_types) |field_name, field_type| { std.debug.print("field name is {s}, field type is {}\n", .{ - field.name, - field.type, + field_name, + field_type, }); } } @@ -107,22 +109,25 @@ const TypeInfo3 = struct { 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, - }; + const fields_len = struct_info.field_names.len; + + // 0.17 的类型信息与 @Struct 的参数形式一致,字段名和字段类型可以直接复用, + // 这里只需要准备新的字段属性 + comptime var field_attrs: [fields_len]std.lang.Type.Struct.FieldAttributes = undefined; + inline for (&field_attrs, struct_info.field_attrs) |*new_attrs, old_attrs| { + // 保留原有属性(例如默认值),仅把对齐改为 1 + new_attrs.* = old_attrs; + new_attrs.@"align" = 1; } // 使用 @Struct 构造新类型(extern 布局,对齐为 1) - return @Struct(.@"extern", null, &field_names, &field_types, &field_attrs); + return @Struct( + .@"extern", + null, + struct_info.field_names, + struct_info.field_types[0..fields_len], + &field_attrs, + ); } const MyStruct = struct { @@ -152,9 +157,8 @@ const hasDecl = struct { pub fn main() void { // true std.debug.print("blah:{}\n", .{@hasDecl(Foo, "blah")}); - // true - // hi 此声明可以被检测到是因为类型和代码处于同一个文件中,这导致他们之间可以互相访问 - // 换另一个文件就不行了 + // false + // 0.17 起 @hasDecl 只对 pub 声明返回 true,即便类型和代码处于同一个文件中也是如此 std.debug.print("hi:{}\n", .{@hasDecl(Foo, "hi")}); // false 不检查字段 std.debug.print("nope:{}\n", .{@hasDecl(Foo, "nope")}); @@ -250,7 +254,7 @@ const Type = struct { // #region Type const std = @import("std"); - // Zig 0.16 使用 @Struct 替代 @Type + // Zig 0.16 起使用 @Struct 替代 @Type const T = @Struct( .auto, // layout null, // BackingInt @@ -268,3 +272,19 @@ const Type = struct { } // #endregion Type }; + +test "typeInfo field names" { + const std = @import("std"); + const info = @typeInfo(typeInfo.T).@"struct"; + try std.testing.expectEqual(2, info.field_names.len); + try std.testing.expectEqualStrings("a", info.field_names[0]); + try std.testing.expectEqual(u8, info.field_types[1]); +} + +test "hasDecl" { + const std = @import("std"); + try std.testing.expect(@hasDecl(hasDecl.Foo, "blah")); + try std.testing.expect(!@hasDecl(hasDecl.Foo, "hi")); + try std.testing.expect(!@hasDecl(hasDecl.Foo, "nope")); + try std.testing.expect(!@hasDecl(hasDecl.Foo, "nope1234")); +} diff --git a/course/code/release/result-location.zig b/course/code/release/result-location.zig index 638cce6f..c8cf3851 100644 --- a/course/code/release/result-location.zig +++ b/course/code/release/result-location.zig @@ -13,7 +13,7 @@ pub fn main() !void { // 以下示例需要 allocator,仅在测试中运行 // try DeclLiteralErrorUnion.main(); // try StdLibArrayList.main(); - try StdLibDebugAllocator.main(); + try StdLibSafeAllocator.main(); } const BasicInference = struct { @@ -225,7 +225,7 @@ const DeclLiteralFunction = struct { const DeclLiteralErrorUnion = struct { // #region decl_literal_error_union const Buffer = struct { - data: std.ArrayListUnmanaged(u32), + data: std.ArrayList(u32), fn initCapacity(allocator: std.mem.Allocator, capacity: usize) !Buffer { return .{ .data = try .initCapacity(allocator, capacity) }; @@ -281,10 +281,10 @@ const StdLibArrayList = struct { // #region stdlib_arraylist const Container = struct { // 使用 .empty 而不是 .{} - list: std.ArrayListUnmanaged(i32) = .empty, + list: std.ArrayList(i32) = .empty, }; - test "ArrayListUnmanaged with decl literal" { + test "ArrayList with decl literal" { var c: Container = .{}; defer c.list.deinit(std.testing.allocator); @@ -296,31 +296,32 @@ const StdLibArrayList = struct { // #endregion stdlib_arraylist }; -const StdLibDebugAllocator = struct { - // #region stdlib_debug_allocator +const StdLibSafeAllocator = struct { + // #region stdlib_safe_allocator pub fn main() !void { - // DebugAllocator 在 0.16 中提供了 .init 声明 - var gpa: std.heap.DebugAllocator(.{}) = .init; - defer _ = gpa.deinit(); + // 0.17 中 SafeAllocator 取代了 DebugAllocator, + // 它的 init 是一个函数,同样可以通过声明字面量 .init(...) 调用 + var safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + defer _ = safe.deinit(); - const allocator = gpa.allocator(); + const allocator = safe.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(); + test "safe allocator with decl literal" { + var safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + defer _ = safe.deinit(); - const allocator = gpa.allocator(); + const allocator = safe.allocator(); const ptr = try allocator.alloc(u8, 100); defer allocator.free(ptr); try std.testing.expectEqual(100, ptr.len); } - // #endregion stdlib_debug_allocator + // #endregion stdlib_safe_allocator }; const NamingConflict = struct { @@ -380,10 +381,10 @@ test "stdlib arraylist" { 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(); +test "stdlib safe allocator" { + var safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + defer _ = safe.deinit(); + const allocator = safe.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/struct.zig b/course/code/release/struct.zig index 0baf2e22..b5b4ad88 100644 --- a/course/code/release/struct.zig +++ b/course/code/release/struct.zig @@ -140,7 +140,8 @@ const SelfReference3 = struct { // #region more_self_reference3 const std = @import("std"); - var gpa = std.heap.DebugAllocator(.{}){}; + // 0.17 使用 SafeAllocator 取代了 DebugAllocator + var safe: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); // #region deault_self_reference3 const User = struct { @@ -183,10 +184,11 @@ const SelfReference3 = struct { pub fn main() !void { // 我们在这里使用了内存分配器的知识,如果你需要的话,可以提前跳到内存管理进行学习! - const allocator = gpa.allocator(); + const allocator = safe.allocator(); defer { - const deinit_status = gpa.deinit(); - if (deinit_status == .leak) std.testing.expect(false) catch @panic("TEST FAIL"); + // deinit 返回泄漏的内存块数量 + const leaks = safe.deinit(); + if (leaks != 0) std.testing.expect(false) catch @panic("TEST FAIL"); } const username = try allocator.alloc(u8, 20); @@ -430,16 +432,22 @@ const PackedCast = struct { try expect(divided.quarter3 == 0x2); try expect(divided.quarter4 == 0x1); + // 0.17 起 @bitCast 只关心“逻辑位”,与目标架构的端序无关: + // 数组的第一个元素对应最低有效位,因此在任何架构上结果都一样 const ordered: [2]u8 = @bitCast(full); + try expect(ordered[0] == 0x34); + try expect(ordered[1] == 0x12); + // 如果需要观察内存中真实的字节排列(与端序相关),可以使用 std.mem.toBytes + const in_memory = std.mem.toBytes(full); switch (native_endian) { .big => { - try expect(ordered[0] == 0x12); - try expect(ordered[1] == 0x34); + try expect(in_memory[0] == 0x12); + try expect(in_memory[1] == 0x34); }, .little => { - try expect(ordered[0] == 0x34); - try expect(ordered[1] == 0x12); + try expect(in_memory[0] == 0x34); + try expect(in_memory[1] == 0x12); }, } } @@ -487,3 +495,7 @@ const reorder_struct = struct { } // #endregion reorder_struct }; + +test "packed cast" { + try PackedCast.main(); +} diff --git a/course/code/release/switch.zig b/course/code/release/switch.zig index 1e2fcfca..b23f9f28 100644 --- a/course/code/release/switch.zig +++ b/course/code/release/switch.zig @@ -147,11 +147,12 @@ const AutoRefer = struct { // 这段函数用来判断一个结构体的字段是否是 optional,同时它也是 comptime 的 // 故我们可以在下面使用inline 来要求编译器帮我们展开这个switch fn isFieldOptional(comptime T: type, field_index: usize) !bool { - const fields = @typeInfo(T).Struct.fields; + // 0.17 起结构体的字段类型单独存放在 field_types 中 + const field_types = @typeInfo(T).@"struct".field_types; return switch (field_index) { // 这里每次都是不同的值 - inline 0...fields.len - 1 => |idx| { - return @typeInfo(fields[idx].type) == .Optional; + inline 0...field_types.len - 1 => |idx| { + return @typeInfo(field_types[idx]) == .optional; }, else => return error.IndexOutOfBounds, }; @@ -160,16 +161,16 @@ fn isFieldOptional(comptime T: type, field_index: usize) !bool { // #region withSwitch const AnySlice = union(enum) { - a: u8, - b: i8, - c: bool, - d: []u8, + a: []const u8, + b: []const i8, + c: []const bool, + d: []const u32, }; fn withSwitch(any: AnySlice) usize { return switch (any) { // 这里的 slice 可以匹配所有的 Anyslice 类型 - inline else => |slice| _ = slice, + inline else => |slice| slice.len, }; } // #endregion withSwitch @@ -186,7 +187,7 @@ fn getNum(u: U) u32 { // 而 tag 则是对应的标签名,这是编译期可知的 inline else => |num, tag| { if (tag == .b) { - return @trunc(num); + return @intFromFloat(num); } return num; }, @@ -259,20 +260,23 @@ const Instruction = enum { fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 { const std = @import("std"); - var stack = try std.BoundedArray(i32, 8).fromSlice(initial_stack); + // std.BoundedArray 已被移除,这里使用基于固定缓冲区的 ArrayList 代替 + var buffer: [8]i32 = undefined; + var stack: std.ArrayList(i32) = .initBuffer(&buffer); + try stack.appendSliceBounded(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().?); + try stack.appendBounded(stack.pop().? + stack.pop().?); ip += 1; continue :vm code[ip]; }, .mul => { - try stack.append(stack.pop().? * stack.pop().?); + try stack.appendBounded(stack.pop().? * stack.pop().?); ip += 1; continue :vm code[ip]; @@ -281,3 +285,33 @@ fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 { }; } // #endregion vm + +test "isFieldOptional" { + const std = @import("std"); + const S = struct { a: u8, b: ?u8 }; + try std.testing.expect(!try isFieldOptional(S, 0)); + try std.testing.expect(try isFieldOptional(S, 1)); + try std.testing.expectError(error.IndexOutOfBounds, isFieldOptional(S, 2)); +} + +test "withSwitch" { + const std = @import("std"); + try std.testing.expectEqual(3, withSwitch(.{ .a = "abc" })); + try std.testing.expectEqual(2, withSwitch(.{ .c = &.{ true, false } })); +} + +test "getNum" { + const std = @import("std"); + try std.testing.expectEqual(42, getNum(.{ .a = 42 })); + try std.testing.expectEqual(3, getNum(.{ .b = 3.7 })); +} + +test "vm" { + const std = @import("std"); + // 栈顶在右侧:先计算 3 + 2 = 5,再计算 7 * 5 = 35 + try std.testing.expectEqual(35, try evaluate(&.{ 7, 2, 3 }, &.{ .add, .mul, .end })); +} + +test "switch main" { + try main(); +} diff --git a/course/engineering/build-system.md b/course/engineering/build-system.md index 730c1cc0..3e2f3f13 100644 --- a/course/engineering/build-system.md +++ b/course/engineering/build-system.md @@ -23,21 +23,29 @@ Zig 使用 `build.zig` 文件来描述一个项目的构建步骤。 <<<@/code/release/build_system/basic/build.zig -`build` 是构建的入口函数,而不是常见的 `main`,真正的 `main` 函数定义在 [`build_runner.zig`](https://github.com/ziglang/zig/blob/master/lib/compiler/build_runner.zig#L15) 中,这是由于 Zig 的构建分为两个阶段: +`build` 是构建的入口函数,而不是常见的 `main`,真正的 `main` 函数由 Zig 的构建系统提供,这是由于 Zig 的构建分为两个阶段: -1. 生成由 [`std.Build.Step`](https://ziglang.org/documentation/master/std/#std.Build.Step) 构成的有向无环图(DAG,即 Directed Acyclic Graph——一种不包含环路的有向图结构,用于表达步骤之间的依赖关系) -2. 执行真正的构建逻辑 +1. 配置阶段(configure):执行 `build.zig`,生成由 [`std.Build.Step`](https://ziglang.org/documentation/master/std/#std.Build.Step) 构成的有向无环图(DAG,即 Directed Acyclic Graph——一种不包含环路的有向图结构,用于表达步骤之间的依赖关系) +2. 执行阶段(make):执行真正的构建逻辑 + +::: info 🅿️ 提示 + +从 Zig 0.17 开始,这两个阶段被拆分到了两个独立的进程中:负责运行 `build.zig` 的配置进程(configurer),以及负责包管理和执行构建图的执行进程(maker)。配置结果会被序列化并缓存,当配置没有变化时,`zig build` 甚至可以跳过 `build.zig` 的执行。 + +因此,`build` 函数应当只“描述”构建图,而不要直接产生副作用(例如在 `build` 函数中直接启动子进程);需要执行的操作应声明为 `Run` 等步骤。如果 `build` 函数的逻辑依赖某个文件或目录的内容,应通过 `b.dependOnFileContents`、`b.dependOnDirectoryContents` 等函数显式声明,以便缓存在其变化时失效。另外,旧的 `b.build_root` 也被替换为 `b.root`。 + +::: > [!TIP] > 第一次接触 Zig 的构建流程,可能会觉得复杂,尤其是构建 Step 的依赖关系,但这是为了后续并发编译作基础。 > -> 如果没有 `build_runner.zig` ,让开发者自己去处理并发编译,将会非常繁琐且容易出错。 +> 如果没有构建系统统一调度,让开发者自己去处理并发编译,将会非常繁琐且容易出错。 `Step` 会在下一小节中会重点讲述,这里介绍一下上面这个构建文件的其他部分: - `b.standardTargetOptions`: 允许构建器读取来自命令行参数的目标配置,并返回可直接传给模块 `.target` 的 `ResolvedTarget`。 - `b.standardOptimizeOption`:允许构建器读取来自命令行参数的**构建优化模式**。 -- `b.addExecutable`:创建一个 [`Build.Step.Compile`](https://ziglang.org/documentation/master/std/#std.Build.Step.Compile) 并返回对应的指针,Zig 0.16 中通常通过 `root_module = b.createModule(...)` 指定入口模块。 +- `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 🅿️ 提示 @@ -68,6 +76,12 @@ C --> B --> A ::: info 🅿️ 提示 +Zig 0.17 移除了 `b.args`。由于构建脚本的配置阶段会被缓存,`--` 之后的参数不再在 `build` 函数中可见,而是通过 `run.addPassthruArgs()` 声明一个占位符,在执行阶段再替换为实际参数。 + +::: + +::: info 🅿️ 提示 + 值得注意的是,`b.installArtifact` 是将构建放入 `install` 这一默认的 step 中。 如果我们想要重新创建一个全新的 install,可以使用 [`b.addInstallArtifact`](https://ziglang.org/documentation/master/std/#std.Build.addInstallArtifact)。 @@ -82,16 +96,22 @@ C --> B --> A zig 提供了四种构建模式(**Build Mode**): -- _Debug_ -- _ReleaseFast_ -- _ReleaseSafe_ -- _ReleaseSmall_ +- _debug_ +- _fast_ +- _safe_ +- _small_ + +:::info 🅿️ 提示 + +Zig 0.17 将这四种模式从 `Debug`、`ReleaseFast`、`ReleaseSafe`、`ReleaseSmall` 更名为 `debug`、`fast`、`safe`、`small`,对应的类型也从 `std.builtin.OptimizeMode` 更名为 `std.lang.Optimize`。命令行参数暂时仍兼容旧名称,但在代码中使用 `==` 或 `!=` 比较时必须使用新名称。 + +::: -如果在 `build.zig` 中使用了 [`standardOptimizeOption`](https://ziglang.org/documentation/master/std/#std.Build.standardOptimizeOption),则构建系统会接收命令行的参数来决定实际构建模式(缺省时为 Debug),参数类型为 `-Doptimize`,例如 `zig build -Doptimize=Debug` 就是以 Debug 模式构建。 +如果在 `build.zig` 中使用了 [`standardOptimizeOption`](https://ziglang.org/documentation/master/std/#std.Build.standardOptimizeOption),则构建系统会接收命令行的参数来决定实际构建模式(缺省时为 debug),参数类型为 `-Doptimize`,例如 `zig build -Doptimize=fast` 就是以 fast 模式构建。 以下讲述四种构建模式的区别: -| Debug | ReleaseFast | ReleaseSafe | ReleaseSmall | +| debug | fast | safe | small | | -------------- | -------------- | -------------- | -------------- | | 构建速度很快 | 构建速度慢 | 构建速度慢 | 构建速度慢 | | 启用安全检查 | 禁用安全检查 | 启用安全检查 | 禁用安全检查 | @@ -99,11 +119,11 @@ zig 提供了四种构建模式(**Build Mode**): | 二进制体积大 | 二进制体积大 | 二进制体积大 | 二进制体积小 | | 无复现构建 | 可复现构建 | 可复现构建 | 可复现构建 | -:::details 关于 Debug 不可复现的原因 +:::details 关于 debug 不可复现的原因 -关于为什么 Debug 是不可复现的,zig 官方手册并未给出具体说明,根据社区的讨论: +关于为什么 debug 是不可复现的,zig 官方手册并未给出具体说明,根据社区的讨论: -在 Debug 构建模式下,编译器会添加一些随机因素进入到程序中(例如内存结构不同),所以任何没有明确说明内存布局的容器在 Debug 构建下可能会有所不同,这便于我们在 Debug 模式下快速暴露某些错误。 +在 debug 构建模式下,编译器会添加一些随机因素进入到程序中(例如内存结构不同),所以任何没有明确说明内存布局的容器在 debug 构建下可能会有所不同,这便于我们在 debug 模式下快速暴露某些错误。 有意思的是,这并不会影响程序正常运行,除非你的程序逻辑有问题。 @@ -154,7 +174,7 @@ Project-Specific Options: 通常,二进制可执行程序的构建结果会输出在 `zig-out/bin` 下,而链接库的构建结果会输出在 `zig-out/lib` 下。 -如果要连接到系统的库,在 Zig 0.16 的模块化构建 API 中通常使用 `exe.root_module.linkSystemLibrary`,Zig 内部借助 pkg-config 实现该功能。类似地,链接其他库或添加 C/C++ 源文件时,也通常是操作 `root_module`。示例: +如果要连接到系统的库,在 Zig 0.16 起的模块化构建 API 中通常使用 `exe.root_module.linkSystemLibrary`,Zig 内部借助 pkg-config 实现该功能。类似地,链接其他库或添加 C/C++ 源文件时,也通常是操作 `root_module`。示例: <<<@/code/release/build_system/system_lib/build.zig @@ -204,7 +224,7 @@ zig 本身提供了一个实验性的文档生成器,它支持搜索查询, 关于所有的 target,可以使用 `zig targets` 查看。 -最常用的一个 target 设置可能是 `b.standardTargetOptions`,它会允许读取命令行输入来决定构建目标 target,并返回一个 [`ResolvedTarget`](https://ziglang.org/documentation/master/std/#std.Build.ResolvedTarget)。在 Zig 0.16 的模块化构建 API 中,这个值通常传给模块的 `.target` 字段。 +最常用的一个 target 设置可能是 `b.standardTargetOptions`,它会允许读取命令行输入来决定构建目标 target,并返回一个 [`ResolvedTarget`](https://ziglang.org/documentation/master/std/#std.Build.ResolvedTarget)。在 Zig 0.16 起的模块化构建 API 中,这个值通常传给模块的 `.target` 字段。 如果需要手动指定一个 target,可以先构建一个 `std.Target.Query`,再通过 `b.resolveTargetQuery` 得到 `ResolvedTarget`,并把解析后的结果传给模块,如: diff --git a/course/engineering/package_management.md b/course/engineering/package_management.md index 1f201eba..9409480f 100644 --- a/course/engineering/package_management.md +++ b/course/engineering/package_management.md @@ -48,7 +48,7 @@ zig 当前并没有一个中心化存储库,包可以来自任何来源,无 目前 zig 已支持通过 [`zig fetch`](../environment/zig-command#zig-fetch) 来获取 hash 并写入到 `.zon` 中! -Zig 0.16 会把抓取到的依赖放在项目根目录旁的 `zig-pkg` 目录,通常不需要提交进仓库。 +Zig 0.16 起会把抓取到的依赖放在项目根目录旁的 `zig-pkg` 目录,通常不需要提交进仓库。Zig 0.17 中,`zig fetch` 默认只抓取到全局缓存;只有带上 `--save` 时才会同时抓取到项目本地的 `zig-pkg` 目录,而 `zig build` 总是会抓取到本地。 ::: diff --git a/course/environment/zig-command.md b/course/environment/zig-command.md index 7e3de498..f9853d43 100644 --- a/course/environment/zig-command.md +++ b/course/environment/zig-command.md @@ -14,6 +14,8 @@ outline: deep 构建项目。该命令会自动在当前及父目录中查找 `build.zig` 文件并执行构建流程。 +从 Zig 0.17 起,构建被拆分为配置和执行两个阶段。可以使用 `zig build --print-configuration` 把配置阶段得到的构建图以 `.zon` 格式输出到标准输出,方便排查构建脚本的问题,更多细节参见 [构建系统](../engineering/build-system)。 + ## `zig build-obj` 将指定的 Zig 源文件编译成对象文件(`.o` 文件)。 @@ -26,7 +28,7 @@ outline: deep 初始化一个新的 Zig 项目。此命令会在当前目录下创建 `build.zig`、`build.zig.zon` 和 `src` 目录(包含 `main.zig` 和 `root.zig`)。 -> **注意**:在 Zig 0.12+ 版本中,原来的 `zig init-exe` 和 `zig init-lib` 命令已合并为统一的 `zig init` 命令。新的模板同时包含可执行文件和静态库的配置,用户可以根据需要删除不需要的部分。 +> **注意**:在 Zig 0.12+ 版本中,原来的 `zig init-exe` 和 `zig init-lib` 命令已合并为统一的 `zig init` 命令。新的模板同时包含一个可供其他项目导入的模块(`src/root.zig`)和一个可执行文件(`src/main.zig`),用户可以根据需要删除不需要的部分。 ```sh . # 项目根目录 @@ -34,9 +36,11 @@ outline: deep ├── build.zig.zon # 项目清单文件 (zon 是 Zig Object Notation):声明项目元数据和依赖项 └── src # 源代码目录 ├── main.zig # 程序主入口文件(可执行文件) - └── root.zig # 库的根文件(静态库) + └── root.zig # 包的根模块文件(可被其他项目导入) ``` +如果只需要最精简的项目骨架,可以使用 `zig init -m`(即 `--minimal`),它只会生成 `build.zig` 和 `build.zig.zon` 两个文件。 + ## `zig ast-check` 对指定的源文件或从标准输入读取的代码进行 AST (抽象语法树) 级别的语法检查。 @@ -45,6 +49,8 @@ outline: deep 格式化 Zig 源代码文件。支持指定文件路径,也支持从标准输入(`stdin`)读取内容。 +Zig 0.17 新增了 `--complexity` 参数,用于统计每个文件的 token 数与 AST 节点数。相比单纯比较行数,它更适合用来衡量一次修改让代码变得更复杂还是更简单。 + ## `zig test` 编译并运行指定源文件中的测试用例。非常适用于单元测试。 @@ -65,6 +71,8 @@ outline: deep 将 C 代码自动转换为 Zig 代码。这是一个强大的功能,可以极大地帮助开发者将现有的 C 代码库迁移到 Zig。 +需要注意,Zig 0.17 已经移除了 `@cImport`。在项目中使用 C 头文件时,应在 `build.zig` 中借助官方的 [translate-c](https://codeberg.org/ziglang/translate-c) 包完成翻译,详见 [与 C 交互](../advanced/interact-with-c)。`zig translate-c` 命令本身仍然保留,适合临时查看或转换单个文件。 + ## `zig targets` 列出 Zig 编译器支持的所有目标架构、操作系统和 ABI (应用程序二进制接口)。 @@ -98,4 +106,8 @@ zig fetch --save git+https://github.com/david-vanderson/dvui.git#main 当包在其 `build.zig.zon` 中定义了 `name` 字段时,`zig fetch` 会自动使用该名称。你也可以使用 `--save=` 来指定一个自定义的依赖名称,例如 `--save=webuizig`。 +如果希望在 `build.zig.zon` 中原样保存传入的 URL,可以改用 `--save-exact`(同样支持 `--save-exact=`)。 + +从 Zig 0.17 起,不带 `--save` 时 `zig fetch` 只会把包抓取到全局缓存,并且不再要求当前目录存在 `build.zig`;带上 `--save`(或其任意变体)时,才会同时抓取到项目本地的 `zig-pkg` 目录,而 `zig build` 总是会抓取到本地。本地目录的位置可以通过 `--pkg-dir` 参数或 `ZIG_LOCAL_PKG_DIR` 环境变量修改,更多细节参见 [包管理](../engineering/package_management)。 + 除了以上介绍的命令,`zig` 还提供了许多其他命令和选项。随着 Zig 语言的不断发展,新的功能和命令也会持续加入,建议您定期查阅 [Zig 官方文档](https://ziglang.org/documentation/master/) 以获取最新信息。 diff --git a/course/examples/echo_tcp_server.md b/course/examples/echo_tcp_server.md index 11ccdbbe..0992757a 100644 --- a/course/examples/echo_tcp_server.md +++ b/course/examples/echo_tcp_server.md @@ -6,7 +6,7 @@ outline: deep 我们来编写一个小小的示例———— Echo TCP Server(TCP 回显 server),帮助我们理解更多的内容。 -> 代码一共也就一百行左右,简洁但不简单! +> 代码一共也就不到一百行,简洁但不简单! ## 前置知识 @@ -18,9 +18,9 @@ Socket(套接字)是计算机网络中用于实现不同计算机或同一 除了常见的 **TCP** 和 **UDP** 外,还有一种叫做 **Unix Socket**,用于在同一台机器上的不同进程间进行通信,并不使用网络协议栈,而是直接在内核中传递数据,比 TCP 和 UDP 更加高效。 -### Zig 0.16 的 `std.Io` +### `std.Io` 接口 -Zig 0.16 的网络示例优先使用标准库的 `std.Io` 接口。`std.Io.Threaded` 提供 I/O 后端;本例使用单线程模式,配合 `std.Io.net` 完成监听、接受连接、读写和关闭。 +从 Zig 0.16 起,标准库的 I/O(包括网络)统一通过 `std.Io` 接口提供,本例也基于它编写。`std.Io.Threaded` 提供 I/O 后端;本例使用单线程模式,配合 `std.Io.net` 完成监听、接受连接、读写和关闭。 ## 思路讲解 diff --git a/course/hello-world.md b/course/hello-world.md index a64d68fd..d1e4a0f1 100644 --- a/course/hello-world.md +++ b/course/hello-world.md @@ -91,7 +91,7 @@ Zig 本身没有内置的 `@print()` 函数,输出功能通常由标准库的 为了保证线程安全,通常需要在共享 `writer` 的外层自行做同步。 -在 Zig 0.16 中,常见做法是使用 `std.Io.Mutex`(需要 `Io` 实例)或基于 `std.atomic.Mutex` 的轻量自旋锁,根据具体场景选择。 +从 Zig 0.16 起,常见做法是使用 `std.Io.Mutex`(需要 `Io` 实例)或基于 `std.atomic.Mutex` 的轻量自旋锁,根据具体场景选择。 我们鼓励你阅读[标准库源码](https://ziglang.org/documentation/master/std/#std.Io.Mutex)来深入了解其工作原理。 diff --git a/course/update/0.17.0-description.md b/course/update/0.17.0-description.md new file mode 100644 index 00000000..590dc072 --- /dev/null +++ b/course/update/0.17.0-description.md @@ -0,0 +1,518 @@ +--- +outline: deep +comments: false +showVersion: false +--- + +# `0.17.0` + +2026/10/2,`0.17.0` 发布,历时 5 个月,有 206 位贡献者参与,一共进行了 925 次提交! + +如果要用一句话概括这个版本,那就是:**`0.17.0` 重做了构建系统,并继续为语言“收口”。** + +这个版本原本被规划为一个短周期版本,但最终的工作量相当可观:构建系统被拆分为“配置进程”和“执行进程”,并引入了面向 IDE 等第三方工具的 **Build Server Protocol**;新 ELF linker 的能力大幅增强,使得绝大多数面向 `x86_64-linux` 的项目都能用上增量编译。同时,语言层面继续清理历史设计:数组乘法 `**`、`errdefer` 捕获、`void{}`、`i0` 被移除,`@cImport` 也在经历了 `0.16.0` 的迁移期后被正式移除。 + +具体的迁移方式请参阅 [0.17.0 升级指南](./upgrade-0.17.0)。 + +## 目标支持 + +`0.17.0` 在目标支持上继续稳步推进,比较值得注意的点有: + +- `aarch64-openbsd` 现在会在 Zig 的 CI 中原生测试;`aarch64-freebsd` 和 `aarch64-netbsd` 的 CI 任务除了 `master` 推送外,现在也会在 PR 上运行 +- 绕过了一个导致大多数 `aarch64-windows` 二进制(包括 Zig 编译器本身)无法正常工作的 LLVM bug +- 面向 `aarch64-openbsd` 时,编译器会强制启用该平台要求的代码加固手段,确保生成的二进制能够真正运行 +- 32 位 ARM 与 SPARC 现在在崩溃和断言失败时也能输出栈回溯(Thumb-only 目标仍有少量工作待完成);在 AArch64 上进行栈展开时,现在可以正确处理指针认证(pointer authentication)指令 +- 新增 `loongarch32-linux-gnu[sf]` 目标支持 +- 64 位 SPARC,尤其是 `sparc64-linux`,现在已经基本可用——这主要得益于新 ELF linker 对该目标的支持已经优于 LLD +- 标准库已移植到 x86-64 上的 x32 ABI 与 64 位 MIPS 上的 N32 ABI。这类 ILP32 ABI 允许使用 64 位指令集,但指针只有 32 位,用可用地址空间换取更低的内存占用与更好的缓存利用率 +- 新增了一些游戏主机的目标信息:`aarch64-switch`、`arm-gba`、`mipsel-psx`、`powerpc-wiiu` +- 新增非常早期的 `xtensa-linux` 支持,目前只能通过 C 后端或实验性的 LLVM 后端使用 +- 使用 C 后端时,标准库现已支持 `arc[eb]-linux`、`csky-linux` 与 `m88k-openbsd`;不使用 libc 时,标准库现已支持 `microblaze[el]-linux`、`sh[eb]-linux` 与 `sparc-linux` +- 所有 PowerPC 目标现在强制使用 `-mabi=ieeelongdouble`。这只是把既成事实正式化:Zig 从未支持过 IBM 的“double-double”`long double` 格式,将来大概率也不会支持。因此本版本**移除了** `powerpc-linux-gnueabi[hf]`(glibc 在该目标上只支持 double-double),`powerpc-linux-musleabi[hf]` 仍然受支持 +- 本版本**移除了** `powerpc64-linux-gnu`:Zig 只支持为 64 位 PowerPC 链接 ELFv2 二进制,而 glibc 并未在大端上正式支持 ELFv2,也不支持 IEEE `long double` +- 几乎所有架构、所有受支持操作系统上的本机 CPU 型号与特性检测都得到了大幅增强 +- 在目标查询语法中,只有当三元组确实使用本机 libc(即省略了 ABI 部分)时,才会进行本机 libc 版本检测,这更符合大家对目标查询的直觉 + +部分目标的基线 CPU 型号也发生了变化: + +| 目标 | 新的基线 CPU 型号 | +| :------------------ | :---------------- | +| `aarch64-haiku` | `cortex_a55` | +| `m68k-*` | `M68030` | +| `mips64-openbsd` | `octeon` | +| `powerpc-netbsd` | `750` | +| `powerpc64-freebsd` | `pwr8` | +| `powerpc64-linux` | `pwr8` | +| `powerpc64-openbsd` | `pwr9` | +| `s390x-*` | `arch11` | +| `sparc-*` | `generic` | +| `sparc-linux` | `v9` | +| `sparc64-*` | `ultrasparc` | +| `xtensa-*` | `esp32` | + +### Tier 系统 + +Zig 依旧把对各目标的支持程度划分为四档(Tier 1 最高)。与上个版本相比,Tier 1 新增了“内置 fuzzer 可以在该目标上工作(如适用)”这一要求: + +- **Tier 1**:所有非实验性语言特性都已知能正常工作;编译器可以**不依赖 LLVM** 直接为该目标生成机器码;内置 fuzzer 可以在该目标上工作(如适用) +- **Tier 2**:标准库的跨平台抽象覆盖了该目标;断言失败和崩溃时可以输出栈回溯;交叉编译时可获得 libc(如适用);CI 在每次推送时都会构建该目标的模块测试 +- **Tier 3**:编译器可借助 LLVM 等外部后端为该目标生成机器码;链接器可以生成该目标的对象文件、库与可执行文件 +- **Tier 4**:编译器只能为该目标生成汇编或 C 源码 + +目前唯一的 Tier 1 目标仍然是 `x86_64-linux`。 + +本版本的支持表中还引入了“过时(obsolescent)”标记:编译器和标准库会尽力维持对这些目标的支持,但这些支持预计最终会被移除。被标记的目标有 `x86-windows`、`x86_64-macos`、`x86_64-maccatalyst`、`thumb-windows`、`x86-freebsd` 与 `x86-illumos`。 + +### 其他附加目标 + +除了 Tier 1–4 这套划分之外,Zig 对下面这些目标也有不同程度的支持,但 tier 系统本身并不完全适用: + +`aarch64-driverkit`、`aarch64[_be]-freestanding`、`aarch64-fuchsia`、`aarch64-hurd`、`aarch64-switch`、`aarch64-uefi`、`alpha-freestanding`、`amdgcn-amdhsa`、`amdgcn-amdpal`、`amdgcn-mesa3d`、`arc[eb]-freestanding`、`arm[eb]-freestanding`、`arm-3ds`、`arm-fuchsia`、`arm-gba`、`arm-uefi`、`arm-vita`、`avr-freestanding`、`bpf(eb,el)-freestanding`、`csky-freestanding`、`ez80-freestanding`、`ez80-tios`、`hexagon-freestanding`、`hppa[64]-freestanding`、`kalimba-freestanding`、`kvx-freestanding`、`lanai-freestanding`、`loongarch(32,64)-freestanding`、`loongarch(32,64)-uefi`、`m68k-freestanding`、`m88k-freestanding`、`microblaze[el]-freestanding`、`mips[64][el]-freestanding`、`mipsel-psx`、`mipsel-psp`、`msp430-freestanding`、`nvptx[64]-cuda`、`nvptx[64]-nvcl`、`or1k-freestanding`、`powerpc-wiiu`、`powerpc[64][le]-freestanding`、`powerpc64-ps3`、`propeller-freestanding`、`riscv(32,64)[be]-freestanding`、`riscv(32,64)-uefi`、`riscv64-fuchsia`、`riscv64-hurd`、`s390x-freestanding`、`sh[eb]-freestanding`、`sparc[64]-freestanding`、`spirv(32,64)-opencl`、`spirv(32,64)-opengl`、`spirv(32,64)-vulkan`、`spork8-freestanding`、`thumb[eb]-freestanding`、`thumb-fuchsia`、`thumb-gba`、`thumb-vita`、`ve-freestanding`、`wasm(32,64)-emscripten`、`wasm(32,64)-freestanding`、`x86[_16,_64]-freestanding`、`x86[_64]-hurd`、`x86[_64]-uefi`、`x86_64-driverkit`、`x86_64-fuchsia`、`x86_64-plan9`、`x86_64-ps4`、`x86_64-ps5`、`xcore-freestanding`、`xtensa[eb]-freestanding`。 + +和上个版本相比,这份名单里新增了 Fuchsia、Hurd、Plan 9、PS4 / PS5、Switch、GBA、PSX、Wii U 以及 eZ80 等一批目标。 + +## 系统最低版本要求 + +标准库对部分操作系统有最低版本要求,这同样会影响 Zig 编译器本身。和 `0.16.0` 相比,macOS 的最低版本从 13.0 提升到了 **15.0**,DragonFly BSD 从 6.0 提升到了 **6.4**: + +| 操作系统(Operating System) | 最低版本要求(Minimum Version) | +| :--------------------------- | :-----------------------------: | +| DragonFly BSD | 6.4 | +| FreeBSD | 14.0 | +| Linux | 5.10 | +| NetBSD | 10.1 | +| OpenBSD | 7.8 | +| macOS | 15.0 | +| Windows | 10 | + +## 语言变动 + +### 语言稳定性进展 + +自 `0.16.0` 发布以来,Zig 在语言稳定化方面取得了很大进展,这是通往 1.0 的路线图中的关键一步。 + +在这一个版本周期内,核心团队讨论并决定了大量语言提案:**接受了约 25 个,拒绝了约 125 个**。截至发布时,Codeberg 上还有 23 个、旧的 GitHub issue 跟踪器上还有 61 个尚未决定的语言提案。虽然仍有一些重大决定有待做出,但这意味着语言设计正在明显地走向定型。 + +### `@bitCast` 的语义被重新定义 + +`0.17.0` 修改了 `@bitCast` 的定义。整数与整数之间、整数与 `packed struct` / `packed union` 之间的转换不受影响,但**涉及数组或向量类型的 `@bitCast` 语义发生了变化**,而且这种变化可能在**不触发编译错误**的情况下改变程序行为,升级时建议逐一审查涉及数组或向量的 `@bitCast`。 + +新的定义是:`@bitCast` 会把一个值的**逻辑位表示**重新解释为另一种类型。拥有逻辑位表示的类型包括 `void`、`bool`、整数(`comptime_int` 除外)、浮点数(`comptime_float` 除外)、以整数为底层类型的 `enum(T)` / `packed struct(T)` / `packed union(T)`,以及由这些类型组成的数组或向量。 + +对整数和浮点数而言,逻辑位表示从最低有效位开始、到最高有效位结束;对数组和向量而言,则按照从第一个元素开始的顺序,把所有元素的逻辑位表示依次拼接起来。 + +这意味着新的 `@bitCast` 在小端目标上与旧行为基本一致,但它**与目标的端序完全无关**——在大端目标上,结果会与以前不同。 + +此外,`@bitCast` 不再允许用于 `extern struct` 和 `extern union`。这类代码通常是想重新解释值在内存中的表示(也就是常说的“type punning”),此时应改用 `@ptrCast` 或 `extern union`。 + +### 语法被形式化并进行了模糊测试 + +Zig 的形式化语法 `grammar.peg` 与实际的语言实现一直存在不少出入,形式化语法可能从未与手写的 tokenizer 和 parser 完全吻合过。 + +这个问题现在被修复了:官方编写了一个工具,以 `grammar.peg` 为输入生成一个简单的递归下降解析器,再把它作为“预言机(oracle)”对手写的 `std.zig.Ast.parse()` 做模糊测试。这保证了语法只有单一的事实来源,也为后续的语法调整和语言规范工作扫清了障碍。 + +### C 翻译迁移到外部包 + +`@cImport` 在 `0.16.0` 中被标记为 deprecated,并在 `0.17.0` 中被**正式移除**。 + +同时,构建系统内置的 `std.Build.Step.TranslateC`(即 `b.addTranslateC`)也被标记为 deprecated,官方推荐改为显式依赖 ZSF 官方维护的 [translate-c](https://codeberg.org/ziglang/translate-c) 包。它与内置构建步骤是同一套实现,但为翻译结果提供了更多配置项,并且拥有独立于 Zig 工具链的发布节奏。 + +### 新增 `@backingInt` 与 `@fromBackingInt` + +新的 `@backingInt` 和 `@fromBackingInt` 内建函数取代了已被标记为 deprecated 的 `@intFromEnum` 与 `@enumFromInt`,并且 `zig fmt` 会自动完成这一升级。 + +- `@backingInt` 适用于所有枚举,以及显式指定了底层整数类型的 bitpack(`packed struct` / `packed union`);对于带标记的联合类型,它会返回当前激活标记的底层整数 +- `@fromBackingInt` 通过结果位置推断结果类型(任意枚举,或显式指定了底层整数类型的 bitpack),参数必须**恰好**是该底层整数类型。对于枚举,如果传入 `undefined` 或无效的标记值,会触发带安全检查的非法行为 + +另外,当 `@bitCast` 的目标类型是枚举时,现在也会对无效的标记值进行安全检查;标准库新增了 `std.meta.BackingInt` 用于获取 `@backingInt` 的结果类型;空枚举由于无法实例化,现在要求以 `noreturn` 作为底层类型。 + +### 新增 `@SpirvType` + +SPIR-V 中有许多类型(例如 image、sampler)在 Zig 的类型系统中并没有对应物。过去引用它们的唯一方式是内联汇编,这导致无法把纹理或存储缓冲区声明为普通的全局变量。 + +`0.17.0` 新增了 `@SpirvType(comptime options: std.lang.Type.Spirv) type`,可以创建 `.sampler`、`.image`、`.sampled_image`、`.runtime_array` 等 SPIR-V 类型。在非 SPIR-V 目标上使用它会产生编译错误。 + +### 数组乘法语法被移除 + +数组乘法语法 `a ** b` 被移除,取而代之的是 `@splat`。例如 `[1]u8{0} ** n` 应改写为 `@as([n]u8, @splat(0))`,或者在有结果类型时直接写 `@splat(0)`。 + +### 新增 `@divCeil` + +新的 `@divCeil` 执行向正无穷方向取整的整数除法,补齐了已有的 `@divTrunc`、`@divFloor` 和 `@divExact`: + +```zig +@divCeil(5, 3) == 2 +@divCeil(-5, 3) == -1 +``` + +和其他除法内建函数一样,调用者需要保证除数不为 0 且结果不会溢出。从此不再需要写 `std.math.divCeil(a, b) catch unreachable` 了。 + +### `@hasDecl` 只对公开声明返回 `true` + +过去,`@hasDecl` 对公开声明以及“与调用处位于同一文件中”的声明都会返回 `true`。现在,它的行为不再取决于调用处所在的文件:**只有 `pub` 声明才会返回 `true`**。 + +### 允许解引用编译期已知长度的切片 + +现在,长度在编译期已知的切片可以直接解引用为数组,或者强制转换为数组指针: + +```zig +const slice: []const u16 = &.{ 1, 2, 3 }; +const array: [3]u16 = slice.*; +const array_ptr: *const [3]u16 = slice; +``` + +### `void{}` 语法被移除 + +`void{}` 不再是合法的语法,请使用 `{}` 代替。 + +### `errdefer` 捕获被移除 + +`errdefer |err| { ... }` 中的捕获语法不再被允许。如果需要观察具体的错误,可以把函数拆成两层,在外层使用 `catch |err|` 处理。 + +### `i0` 被移除 + +`i0` 不再是合法的整数类型。这个类型本身没有意义,所以并没有直接的替代品,不过几乎所有用到它的地方都可以透明地替换为 `u0`。 + +### 全局链接属性 `internal` 与 `link_once` 被移除 + +`std.lang.GlobalLinkage` 中的 `internal` 和 `link_once` 被移除了,因为它们语义不清,代码生成和链接方面的支持也不完整。`link_once` 的用途大多可以用 `weak` 替代;至于 `internal`,只需一开始就不要 `@export` 该符号即可。 + +## 标准库 + +先看一些零散的改进: + +- `@exp` 与 `@exp2` 新增了对 `f128` 的支持 +- 新增 `std.Io.Semaphore.waitTimeout` +- 新增用于图像采样、查询与写入的 `std.spirv` 辅助函数 +- `ArrayHashMap.setKey` 不再重新计算整个索引 +- `std.Target.parseCpuModel` 现在返回可选值,而不是错误 +- `std.debug.Pdb` 会对内联的源码位置去重 +- 对 `hash.crc` 命名空间进行了全面审查 +- `std.fs.path` 为 `relative` 和 `resolve` 新增了追加(appending)版本(`std.fs.path` 自 `0.16.0` 起已被标记为 deprecated,推荐通过 `std.Io.Dir.path` 使用) + +### 弃用与移除 + +- `std.builtin` 被标记为 deprecated,改为 `std.lang` +- `std.meta.fieldInfo`、`std.meta.fieldNames`、`std.meta.fieldTypes` 被标记为 deprecated,改为直接使用 `@typeInfo` +- `std.DoublyLinkedList.pop` 被标记为 deprecated,改为 `std.DoublyLinkedList.popLast` +- `std.gpu` 更名为 `std.spirv` +- `std.heap.memory_pool.AlignedManaged` 与 `ExtraManaged` 被移除,改为 `std.heap.memory_pool.Aligned` 与 `Extra` +- `std.ascii.indexOfIgnoreCase` 系列被移除,改为 `std.ascii.findIgnoreCase` 系列 +- `std.bit_set.Integer`、`std.bit_set.Array`、`std.enums.EnumSet` 的 `initEmpty` / `initFull` 被移除,改为 `empty` / `full` +- `std.mem.containsAtLeastScalar2` 被移除,改为 `std.mem.containsAtLeastScalar` +- `std.mem.readPackedIntNative` / `readPackedIntForeign` / `writePackedIntNative` / `writePackedIntForeign` 被移除,改为 `std.mem.readPackedInt` / `writePackedInt` + +### `SafeAllocator` 取代 `DebugAllocator` + +`std.heap.DebugAllocator` 被一个线程安全的新分配器 `std.heap.SafeAllocator` 取代,`std.heap.DebugAllocator` 与 `std.heap.Check` 均被标记为 deprecated。`SafeAllocator` 提供以下保证: + +- `deinit` 会报告所有泄漏,并释放所有后备内存 +- 所有分配不匹配的情况都会导致 panic 或段错误 +- 来自其他 `SafeAllocator` 实例的分配会触发 panic(当 `Options.canary` 不同时) +- 重复释放以及 resize / remap / free 之间的竞争会导致 panic 或段错误 +- 只要后备分配器不复用内存,它自己也不会复用内存,因此大多数“释放后写入”都会导致段错误,或者最终被检测到并 panic + +每次分配后面都跟随一个 `AllocFooter`,其中存放着分配的元数据和栈追踪信息,并通过校验和保护,以便捕获越界写入造成的破坏。官方给出的基准测试显示,在使用该分配器构建标准库测试时,耗时减少了约 25%,峰值内存占用减少了约 50%。 + +### `StackFallbackAllocator` 被重做 + +“小向量(small vec)”优化中常用的“栈缓冲区 + 回退到堆分配”的分配器被重新设计。旧设计存在几个问题:无法指定缓冲区的对齐;分配器对缓冲区大小是泛型的;调用 `.get()` 会改变分配器本身的状态,与其他分配器不一致,因此需要额外的运行时安全检查。 + +现在,缓冲区像大多数标准库 API 一样由调用者作为参数传入。需要注意的是,发布说明中仍沿用了 `StackFallbackAllocator` 这个名字,但在 `0.17.0` 的标准库中,它的实际名称是 `std.heap.BufferFirstAllocator`,旧的 `std.heap.stackFallback` 已不复存在。 + +### `ArrayList` + +- `getLastOrNull` 被标记为 deprecated,并更名为 `last` +- `getLast` 被标记为 deprecated,请改用 `last().?` +- 新增 `lastPtr`,返回 `?*T` + +此外还新增了指针稳定性检查,用于更快地定位 `ArrayList` 的误用问题。 + +### `debug.SafetyLock` 支持共享锁 + +已有的 `lock` 与 `unlock` 依旧是独占锁,适用于可能修改数据的场景;新增的 `lockShared` 与 `unlockShared` 可用于“多个使用者只读、不修改数据”的场景。 + +### `fmt.allocPrint` 迁移到 `mem.Allocator` + +`std.fmt.allocPrint(gpa, ...)` 被标记为 deprecated,现在可以直接在分配器上调用 `gpa.print(...)`;`allocPrintSentinel` 对应 `printSentinel`。 + +### 格式化打印增强 + +用于把字符串转义成可以放进双引号字符串字面量的 `{q}` 说明符放宽了转义规则,UTF-8 编码的数据现在可以原样通过;新增 `{qf}`,用于对某个 `format()` 的输出进行双引号转义。 + +### `std.zon.parse` 被重做 + +`std.zon.parse` 现在接受结构体参数,并从 arena 中分配结果。部分方法也被改名:`fromSliceAlloc` 改为 `fromSlice`,原来的 `fromSlice` 改为 `fromSliceNoAlloc`,其他“from”系列方法也按相同规则改名。 + +另外新增了 `updateFromSlice` 等“updateFrom”系列方法,它们会用 ZON 源中指定的字段覆盖内存中已有值的对应字段。这在按不同优先级加载配置文件时非常有用,例如文本编辑器同时拥有全局配置和项目级配置的场景。 + +### `bit_set` 类型改名 + +为了命名一致,`bit_set` 中的类型被改名,旧名称以及 managed 版本被标记为 deprecated: + +| 旧名称 | 新名称 | +| :----------------------------------------------------------------- | :----------------------------------------- | +| `std.bit_set.IntegerBitSet` | `std.bit_set.Integer` | +| `std.bit_set.ArrayBitSet` | `std.bit_set.Array` | +| `std.StaticBitSet`、`std.bit_set.StaticBitSet` | `std.bit_set.Static` | +| `std.DynamicBitSetUnmanaged`、`std.bit_set.DynamicBitSetUnmanaged` | `std.bit_set.Dynamic` | +| `std.DynamicBitSet`、`std.bit_set.DynamicBitSet` | `std.bit_set.DynamicManaged`(deprecated) | + +### `std.lang.Type` 采用“数组结构体”风格 + +进行类型反射时,结构体、联合等类型的信息现在以**数组结构体(Struct-Of-Arrays)**的形式返回:原来由 `StructField` 等组成的 `fields` 数组,被拆分成了 `field_names`、`field_types`、`field_attrs` 等多个并列的切片。这与 `0.16.0` 引入的 `@Struct`、`@Union` 等类型构造内建函数的参数形式保持了一致。 + +### `lang.OptimizeMode` 更名为 `lang.Optimize` + +`std.lang.OptimizeMode` 更名为 `std.lang.Optimize`,并去掉了枚举标签中的“release”字样:`Debug` → `debug`、`ReleaseSafe` → `safe`、`ReleaseFast` → `fast`、`ReleaseSmall` → `small`。 + +虽然功能上没有变化,并且提供了向后兼容的声明,但这依然是一个破坏性变更:使用 `==` 或 `!=` 与旧名称比较的表达式将无法编译。 + +同时,推荐使用 `std.lang.Optimize.runtimeSafety` 代替 `std.debug.runtime_safety`,因为前者能让调用处感知到自身所在模块的设置,而不是标准库模块的设置。 + +### `@import("builtin")` 中的冗余常量被弃用 + +`@import("builtin")` 中冗余的 `cpu`、`os`、`abi` 和 `object_format` 被标记为 deprecated,并将在 `0.18.0` 中移除,请改用 `target` 常量上对应的字段:`target.cpu`、`target.os`、`target.abi`、`target.ofmt`。 + +### `mem.eql` 与 `mem.findDiff` 正确处理浮点数 + +`std.mem.eql` 和 `std.mem.findDiff` 在两个输入是同一块内存的切片时会走捷径直接返回。但这个捷径只有在该类型的 `==` 满足自反性时才正确,而浮点数并不满足(例如 `nan != nan`)。现在处理浮点切片时会禁用这个优化。 + +### `Uri` 与 `net.HostName` 解耦 + +`Uri` 遵循的 RFC 3986 与 `HostName` 遵循的 RFC 1123 对“合法主机名”的定义大不相同,因此 `Uri` 中所有与 `HostName` 相关的内容都被移除:`Uri.getHost` 移动为 `HostName.fromUri`(由于语义差异较大,没有提供平滑的弃用过渡),`Uri.getHostAlloc` 被直接移除。 + +## 构建系统 + +`0.17.0` 的构建系统经历了一次大规模重做,先看一组 API 变动: + +- `b.build_root`(`Directory`)改为 `b.root`(`Cache.Path`) +- `ConfigHeader.Options` 中的 `include_guard_override` 改为 `include_guard` +- `LazyPath.getDisplayName` 改为 `format`(使用 `"{f}"` 打印) +- `LazyPath.basename` 被移除,因为该值在执行阶段之前是未知的 +- `b.findProgram` 被拆分为 `findProgram` 与 `findProgramLazy`,API 也做了面向未来的调整 +- `ConfigHeader` 被修复,现在所有风格都会报告未使用的值 +- `Run` 步骤中带 `Prefixed` 的一系列参数方法被合并,统一改为带选项的 `...Arg2` 版本,例如 `addArtifactArg2`、`addOutputFileArg2`、`addFileArg2`、`addDirectoryArg2` 等 + +### 配置进程与执行进程分离 + +`zig build` 现在会在一个独立的可执行文件中运行项目的 `build.zig`(配置进程,configurer),而包管理和构建图的执行则由另一个进程负责(执行进程,maker)。这让 `zig build` 在多个方面变得更快: + +- 修改 `build.zig` 时,maker 可执行文件不会改变,因此安装 Zig 之后只需要构建一次(“首次设置”) +- maker 可执行文件以开启优化的方式构建,在引入了 `--watch` 和 `--fuzz` 之后,这一点变得越来越有价值 +- 根据 `zig build` 使用的命令行参数,有时可以完全跳过 `build.zig` 的执行 + +此外,配置结果现在会被序列化为一种紧凑的二进制格式,可供第三方工具使用,它也是新的 Build Server Protocol 的一部分。过去通过 fork build runner 来满足这类需求的做法不再受支持。可以使用 `--print-configuration` 以 `.zon` 格式把配置输出到标准输出。 + +### 缓存系统重做 + +缓存系统新增了对目录的支持(目录中条目的增删和重命名可以导致缓存未命中),以及“元数据模式”(大小、inode 或 mtime 变化时,无论内容是否变化都视为未命中)。这两种特性可以任意组合,并作为新 API 暴露在构建系统中。 + +缓存清单改用二进制格式,文件大小减少约 25%;新的 `zig cache-cat` 子命令可以用于排查或查看 `.zig-cache` 目录中的文件。缓存系统现在还能解释一次“未命中”的原因。在实测中,缓存命中的速度提升了 5–10%。 + +### 引入“配置缓存被污染”的概念 + +如果 `build.zig` 中的配置逻辑产生了副作用,或者做了缓存系统无法追踪的事情,就称配置缓存“被污染(poisoned)”了。注意它和构建步骤在执行时是否有副作用无关:一个打印“hello world”的 `Run` 步骤不会污染缓存,而在配置阶段检查 `scdoc` 是否存在、并据此决定某个选项的默认值,则会污染缓存。 + +保持缓存“纯净”可以让 `zig build` 在配置未变化时直接跳过配置进程。缓存被污染时,执行进程在读取完配置后会将其删除,因为它无法被复用。调用 `findProgram`,或者更直接地调用 `std.Build.Graph.poisonCache`,都会污染缓存。更好的做法是通过以下新函数显式声明配置阶段的依赖: + +- `std.Build.dependOnFileContents`:配置逻辑依赖某个文件的内容 +- `std.Build.dependOnFileMetadata`:配置逻辑依赖某个文件的大小、inode、mtime 和内容 +- `std.Build.dependOnDirectoryContents`:配置逻辑依赖某个目录中的条目 +- `std.Build.dependOnDirectoryMetadata`:配置逻辑依赖某个目录的最后修改时间 + +高级用户还可以通过 `--cache-poison[=mode]` 覆盖这一行为,可选值有 `pure`(默认,避免错误的缓存命中)、`poisoned`(不缓存配置)、`disallowed`(缓存将被污染时直接 panic)和 `ignored`(无视污染)。 + +### `findProgram` 与 `findProgramLazy` + +`findProgram` 会在配置阶段立即在主机上查找一个可能有多个名字的可执行文件,先查找搜索前缀,再查找 `PATH` 环境变量。由于它会污染配置缓存,只适合配置逻辑确实需要观察程序是否存在(或其输出)的场景。 + +`findProgramLazy` 则会创建一个匿名的 `Step` 来完成查找,返回一个 `LazyPath`。它不会污染配置缓存,但其结果无法在配置阶段使用;只有当这个 `LazyPath` 被某个依赖它的步骤用到时,查找才会真正发生。它适合二进制在不同系统上名字不同(例如 `python` 与 `python3`),或者二进制可能由源码构建而来的场景。 + +### `Run` 步骤:透传参数 + +`b.args` 被移除,`--` 之后的透传参数改为通过 `run.addPassthruArgs()` 统一添加。构建脚本因此无法再在配置阶段观察到这些参数,但作为交换,修改这些参数时不再需要重新执行构建脚本。 + +### `Fmt` 步骤的选项 + +`paths` 与 `exclude_paths` 现在是 `LazyPath` 列表,可以使用便捷函数 `b.pathList` 创建。 + +### `Step.Options` 新增 `addOptionPathDirectory` + +添加文件路径类型的选项时,现在需要显式选择:`addOptionPath`(必须是文件)、`addOptionPathDirectory`(必须是目录)或 `addOptionPathUntracked`(不参与依赖追踪)。 + +### 惰性依赖更易用 + +- 抓取惰性依赖时会输出日志 +- `std.Build.dependency` 现在支持惰性依赖 +- 新增 `std.Build.dependencyLazy`,它可能返回 `error.LazyDependencyNeeded` 而不是 `null`,因此可以配合 `try` 使用 +- 当用户的 `build` 函数返回 `error.LazyDependencyNeeded` 时,构建系统会去抓取依赖,而不是让配置失败 + +### 移除了覆盖 build runner 的能力 + +“build runner”这个概念已经不复存在,它被拆分成了 configurer 与 maker。过去需要覆盖 build runner 的用例,现在由 Build Server Protocol 来满足。 + +### 包管理 + +所有包管理功能都从编译器中移到了构建系统里,涉及 `zig build`、`zig fetch`、`zig init`、`zig libc` 与 `zig cache-cat` 等子命令。这意味着包抓取逻辑、HTTP 客户端与网络、TLS 与相关加密算法、git 协议、xz / gzip / zstd / flate / zip 解压,以及 `build.zig.zon` 的解析和校验,现在都以源码形式随 Zig 分发,并以 `-Osafe` 模式(而非 `-Ofast`)编译。开发构建系统本身时,可以设置环境变量 `ZIG_DEBUG_CMD=1` 以调试模式编译。 + +其他变化: + +- 修复了路径依赖可以逃逸出父包根目录的 bug +- `--pkg-path` 命令行参数和 `ZIG_LOCAL_PKG_DIR` 环境变量现在对 fetch 和 build 命令都生效 +- `zig fetch` 现在只抓取到全局缓存;只有使用 `--save`(或其变体)时,才会同时抓取到项目本地的包目录(默认是 `zig-pkg`)。全局抓取时不再要求存在 `build.zig`,这也修复了 `zig fetch .` 的回归问题;而 `zig build` 总是会抓取到本地(同时也会抓取到全局) + +### Windows 下 DLL 参数的 `PATH` 处理略有变化 + +以前以 Windows 或 Wine 为目标时,`Run` 步骤中添加的 artifact 参数会根据其递归依赖的 DLL 所在目录修改 `PATH`。现在只会对 `argv[0]` 这样处理。 + +### Build Server Protocol + +传入 `--listen=-` 时,构建系统会提供一个协议服务,允许连接的客户端在构建图执行期间对其进行监视和控制。它主要面向 IDE 等第三方工具,目前可以: + +- 获取完整的构建图在配置完成后的静态信息,例如有哪些构建步骤、设置了哪些选项、依赖关系等(暴露的模块名集合等少数信息尚未包含) +- 在构建步骤开始和完成时收到通知,包括错误信息以及生成了哪些文件 +- 请求构建指定的步骤 + +官方预计今后 Zig 自己的许多构建工具也会成为 Build Server Protocol 的客户端,并计划让构建服务器复用编译器服务器协议,为编辑器提供类型系统信息、重构等高级能力。 + +## 编译器 + +### 增量编译 + +`0.17.0` 大幅改进了增量编译:修复了大量 bug,上个版本引入的新 ELF linker 也已经很好地支持了这一特性。 + +因此,现在大多数面向 `x86_64-linux` 的项目都可以使用增量编译。只需在 `zig build` 命令后加上 `-fincremental --watch`(例如 `zig build -fincremental --watch`),构建系统就会监听源文件的变化,并在变化时执行增量重新构建。 + +后续版本将继续改进该特性,包括引入新的 Mach-O linker 和对增量编译支持良好的自托管 aarch64 后端、支持不配合 `--watch` 使用增量编译,以及修复剩余的 bug。 + +### SPIR-V 后端 + +自托管的 SPIR-V 后端现在和其他后端一样支持多线程。`LocalSize`、`OriginUpperLeft` 等执行模式现在由函数的调用约定推导,而不是通过内联汇编设置;新增的 `spirv_task` 和 `spirv_mesh` 调用约定支持了 task shader 与 mesh shader。 + +不再允许在内联汇编中通过 `OpCapability` 和 `OpExtension` 声明 capability 和扩展,改为通过目标 CPU 特性(即 `-mcpu` 选项)启用。这个周期内一共修复了 22 个 SPIR-V 后端的 bug。 + +### aarch64 后端 + +该后端的进展受制于链接器的改进,而其中许多改进已在本周期完成。 + +### loongarch 后端 + +社区贡献了 loongarch64 自托管后端的初始实现,目前仍处于实验阶段,尚不可用。 + +### WebAssembly 后端 + +Zig 的 WebAssembly 后端现在已经通过了 100% 的行为测试(相对于 LLVM 后端)。但由于缺乏调试信息支持,它目前还不是调试模式下的默认后端。 + +## 链接器(Linker) + +### ELF + +本版本在“用新实现取代旧的自托管 ELF linker”方面取得了显著进展,具体包括:完整的 x86_64 与 SPARC64 支持、部分 LoongArch 支持、静态库与动态库生成、可执行文件中未定义符号的报错、GOT 生成、copy relocation、GNU 符号版本、DWARF 调试信息、符号哈希表生成、基本可复现的二进制、任意的段对齐,以及对较小主机文件系统块大小的支持。 + +虽然它还没有完全达到旧版自托管 ELF linker 的功能水平(因此仍默认关闭),但实际上已经能够构建绝大多数面向 `x86_64-linux` 的 Zig 项目。和 `0.16.0` 一样,在使用增量编译时会默认启用这个新链接器。官方希望在下一个版本中彻底移除旧的 ELF linker。 + +### COFF + +链接器的 COFF 支持得到了大量增强,包括:输出 `.obj` 对象文件与 `.lib` 归档,随映像一起输出导入库,输出 TLS 与导出数据目录,接受对象文件、归档和导入库作为输入,按需从归档中链接对象,支持 COMDAT 规则,同时支持链接 `-gnu` 与 `-msvc` 的 libc,支持 TLS 与 `__dllimport`,根据导出符号自动选择入口点,`-gnu` 下的构造 / 析构函数支持,以及 `/INCLUDE`、`/ALTERNATENAME`、`/MERGE`、`/DEFAULTLIB` 等 `.drectve` 参数。 + +### 新的链接器测试框架 + +Zig 的链接器测试正在转向基于快照的方式:结合 objdump 快照对比、实际运行产物以及检查链接器错误来进行测试。 + +### SPIR-V + +SPIR-V 链接器被重写,现在支持增量编译,并且可以链接外部的 `.spv` 对象文件。 + +## Fuzzer(模糊测试器) + +尽管本版本的构建系统改动与内置 fuzzer 及其和构建系统的交互有一定关系,但 fuzzer 本身没有变化。官方预计会在之后的版本周期中重点改进它。 + +## Bug 修复 + +这个版本周期内一共关闭了 329 个 bug 报告。 + +### 这个版本仍然包含已知 bug + +Zig 仍然存在已知的 bug、错误编译和回归问题。即使使用 `0.17.x`,在一个不算小的项目中使用 Zig,也可能需要你参与到开发流程中来。当 Zig 到达 1.0.0 时,Tier 1 支持将额外增加一条 bug 策略方面的要求。 + +`0.17.0` 中值得注意的已知回归有: + +- #37006:x86 上软浮点的 compiler-rt 无法编译 +- #36986:`std.debug.simple_panic` 无法编译;Zig libc 中的弱符号无法被可靠地覆盖 +- #36444:配置进程与执行进程的分离破坏了 `std.Build.Step.Run` 中使用响应文件(response file)的场景 +- #37050:SPIR-V 后端的回归 + +## 工具链(Toolchain) + +### LLVM 22 + +本版本升级到了 LLVM `22.1.8`,这同样覆盖了 Clang(`zig cc`)、libc++、libc++abi、libunwind 和 libtsan。 + +#### loop vectorization 仍然被关闭 + +上个版本为了绕过一个影响 Zig 编译器的错误编译问题,被迫关闭了 LLVM 的关键优化 pass——loop vectorization。虽然修复已经合入 LLVM 主分支,但 `0.17.0` 使用的 LLVM 22 并不包含该修复,因此这个变通方案仍然保持开启。`0.18.0` 将升级到 LLVM 23,届时可以重新启用这项优化。 + +### musl 1.2.5 + +`0.17.0` 分发的是 musl `1.2.5`,并附带了向后移植的安全与可移植性修复;上游已经发布了 `1.2.6`,`0.18.0` 将会更新到这个版本。静态链接 musl 时,许多函数现在由 zig libc 提供,而不是从 musl 复制而来的源文件。因此如果你遇到了 Zig 提供的 musl libc 的问题,请向 Zig 的 issue 跟踪器报告,而不是 musl 的。 + +### glibc 2.44 + +交叉编译时现在可以使用 glibc `2.44`。 + +### Linux 7.2 headers + +本版本包含 Linux 内核 `7.2` 版本的头文件。 + +### macOS 27.0 headers + +本版本包含 macOS `27.0` 版本的系统头文件。 + +### MinGW-w64 + +Zig 分发的 MinGW-w64 更新到了提交 `31bd54ab7d5fe03c67ed2bb1a57e531b9c7f8cc4`,同样地,许多函数现在由 zig libc 提供。 + +### NetBSD 11.0 libc 与 OpenBSD 7.9 libc + +交叉编译时现在可以使用 NetBSD libc `11.0` 与 OpenBSD libc `7.9`。 + +### WASI libc + +`0.17.0` 继续分发 WASI libc 提交 `c89896107d7b57aef69dcadede47409ee4f702ee`,其中许多函数现在也由 zig libc 提供。从 `0.18.0` 开始,Zig 将不再分发第三方的 WASI libc 代码,而是通过 zig libc 为 WASI 目标提供 libc。 + +### zig libc + +在 `libc.txt` 文件中,`gcc_dir` 字段更名为 `cc_dir`,以反映它并不专属于 GCC 的事实。旧名称暂时仍然可以使用,但建议尽快迁移。另外,`cc_dir` 现在在 Linux 目标上是必填的;由于 Android 和 OpenHarmony 把相关的对象文件放在了不寻常的位置,这些目标的用户可能需要把 `cc_dir` 设置为与 `crt_dir` 相同的路径。 + +### zig cc + +`zig cc` 与 `zig c++` 现在基于 Clang `22.1.8`。 + +### zig objdump + +新增了 `zig objdump` 子命令。它是链接器快照测试的基础,也为 COFF 链接器的开发提供了帮助,支持输出文件头、段头、重定位、符号、导入与导出符号、归档成员等信息。 + +### resinator + +Windows 资源脚本的编译将在下一个版本中从编译器移到一个官方但独立的构建系统包中。因此,相应的 `std.Build` 函数与字段(例如 `Build.Module.addWin32ResourceFile`)已在本版本中被标记为 deprecated。`zig rc` 子命令会被保留,以继续支持在其他构建系统中使用 Zig 工具链的场景。 + +### zig fmt + +新增了 `--complexity` 参数,用于统计源文件的 token 数与 AST 节点数。相比单纯的行数,它能更有效地衡量一次修改让代码复杂度增加还是减少了。例如,把 `std.fmt.allocPrint` 改为 `arena.print` 之后,行数几乎不变,但 `--complexity` 显示源码复杂度降低了约 1%。 + +## 路线图(Roadmap) + +官方给出的后续方向是: + +- **与 ZLS 团队合作完善 Build Server Protocol**,直到满足 ZLS 的所有使用场景 +- **完成 x86_64 后端的 Windows 支持**,使其可以默认启用 +- **完成并稳定语言本身** +- **做完 aarch64 后端**,并让它成为调试模式的默认后端 +- **继续增强链接器**,摆脱对 LLD 的依赖,并支持增量编译 +- **增强内置 fuzzer**,让它可以与 AFL 等业界最先进的 fuzzer 竞争 +- **把对 LLVM 的依赖从“链接库”转为“调用 Clang 进程”** +- **完善构建系统**,尤其是包管理功能 +- **审查标准库** + +如果说 `0.16.0` 是“大量基础设施重构真正落地”,那么 `0.17.0` 就是在这个基础上,把构建系统重新打磨成了一个更快、更可缓存、也更容易被外部工具集成的系统,同时让语言本身离“定型”又近了一步。 diff --git a/course/update/upgrade-0.17.0.md b/course/update/upgrade-0.17.0.md new file mode 100644 index 00000000..6f7e5c37 --- /dev/null +++ b/course/update/upgrade-0.17.0.md @@ -0,0 +1,719 @@ +--- +outline: deep +showVersion: false +--- + +本篇文档将介绍如何从 `0.16.0` 版本升级到 `0.17.0`。 + +和 `0.16.0` 那次以 `std.Io` 为核心的大迁移相比,`0.17.0` 对日常业务代码的冲击要小一些,但仍然有几类必须处理的破坏性变更: + +- **语法清理**:数组乘法 `**`、`errdefer |err|`、`void{}`、`i0` 被移除 +- **`@cImport` 被正式移除**,C 头文件翻译需要迁移到构建系统中的 translate-c 包 +- **类型反射改为“数组结构体”风格**,`@typeInfo(T).@"struct".fields` 等写法需要改写 +- **`@bitCast` 对数组 / 向量的语义变为与端序无关**,可能在没有编译错误的情况下改变行为 +- **构建系统拆分为配置进程与执行进程**,`b.build_root`、`b.args` 等 API 被移除,配置阶段的副作用需要显式声明 +- **分配器重做**:`DebugAllocator` → `SafeAllocator`,`stackFallback` → `BufferFirstAllocator` + +::: tip 🅿️ 推荐的升级顺序 + +1. 先手动处理**无法再被解析**的语法:数组乘法 `**` 和 `errdefer |err|`。`0.17.0` 的 `zig fmt` 遇到它们会直接报错;`void{}` 与 `i0` 仍然可以被解析,可以留到编译时按错误提示修复 +2. 运行一次 `zig fmt`,它会自动把 `@intFromEnum` / `@enumFromInt` 升级为 `@backingInt` / `@fromBackingInt` +3. 修复 `build.zig`,确保构建脚本本身能够跑起来 +4. 按编译错误逐个修复标准库与反射相关的 API +5. 最后审查所有涉及数组或向量的 `@bitCast`,这一类问题**不会产生编译错误** + +::: + +## 语言变动 + +### 数组乘法 `**` 被移除 + +数组乘法语法 `a ** b` 被移除了。最常见的用法——用同一个值填充数组——应当改为 `@splat`: + +```zig +// 0.16.0 +var buffer = [_]u8{0} ** 1024; +const line = "-" ** 40; + +// 0.17.0 +var buffer: [1024]u8 = @splat(0); +const line: [40]u8 = @splat('-'); +``` + +`@splat` 依赖结果类型,因此需要显式写出数组类型。如果没有结果位置,可以配合 `@as` 使用:`@as([1024]u8, @splat(0))`。需要哨兵时也可以直接写:`const s: [40:0]u8 = @splat('-');`。 + +对结构体字段同样适用: + +```zig +pub const init: RollingIntegralImage = .{ + // 0.16.0: .data = [1]Float{0} ** data_size, + .data = @splat(0), + .num_rows = 0, +}; +``` + +如果重复的是**多个元素组成的模式**,次数较少时可以直接用 `++` 拼接,次数较多时可以写一个编译期辅助函数: + +```zig +const small = [_]u8{ 1, 2 }; +const big = small ++ small ++ small; // { 1, 2, 1, 2, 1, 2 } + +fn repeat(comptime T: type, comptime pattern: []const T, comptime n: usize) [pattern.len * n]T { + var result: [pattern.len * n]T = undefined; + for (0..n) |i| @memcpy(result[i * pattern.len ..][0..pattern.len], pattern); + return result; +} + +const pat = repeat(u8, &.{ 1, 2 }, 3); // { 1, 2, 1, 2, 1, 2 } +``` + +### `errdefer` 捕获被移除 + +`errdefer |err|` 不再被允许。官方给出的迁移方式是**把函数拆成两层**:内层保留原来的逻辑(包括不带捕获的 `errdefer`),外层通过 `catch |err|` 观察错误: + +```zig +// 0.16.0 +fn processOneTarget(job: Job) void { + errdefer |err| std.debug.panic("panic: {s}", .{@errorName(err)}); + const target = job.target; + // ... +} + +// 0.17.0 +fn processOneTarget(job: Job) void { + processOneTargetInner(job) catch |err| std.debug.panic("panic: {s}", .{@errorName(err)}); +} + +fn processOneTargetInner(job: Job) !void { + const target = job.target; + // ... +} +``` + +如果原来的 `errdefer |err|` 只是用来记录日志,外层可以在记录后继续把错误返回: + +```zig +fn processOne(fail: bool) !void { + processOneInner(fail) catch |err| { + std.log.err("failed: {t}", .{err}); + return err; + }; +} + +fn processOneInner(fail: bool) !void { + // 不需要观察错误的清理逻辑可以继续使用 errdefer + errdefer std.log.debug("cleanup", .{}); + if (fail) return error.Oops; +} +``` + +### `void{}` 与 `i0` 被移除 + +这两项都是简单的替换: + +- `void{}` 改为 `{}` +- `i0` 改为 `u0`(`i0` 本身没有意义,几乎所有用法都可以透明地替换为 `u0`) + +### `@cImport` 被移除,改用 translate-c 包 + +`@cImport` 在 `0.16.0` 中已经被标记为 deprecated,`0.17.0` 将其正式移除。同时,构建系统内置的 `b.addTranslateC` 也被标记为 deprecated(暂时仍可使用),官方推荐改为依赖 ZSF 官方维护的 [translate-c](https://codeberg.org/ziglang/translate-c) 包。 + +首先把它添加为依赖。注意 **translate-c 的版本需要与 Zig 版本匹配**,适配 `0.17.0` 的是 `2.0.0` 版本: + +```sh +zig fetch --save git+https://codeberg.org/ziglang/translate-c#2.0.0 +``` + +然后准备一个头文件作为翻译入口,把原来写在 `@cImport` 里的内容搬进去: + +```c +// src/c.h +#define _NO_CRT_STDIO_INLINE 1 +#include +#include +``` + +最后在 `build.zig` 中翻译该头文件,并把翻译结果作为模块导入: + +```zig +const Translator = @import("translate_c").Translator; + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const translate_c = b.dependency("translate_c", .{}); + const c: Translator = .init(translate_c, .{ + .c_source_file = b.path("src/c.h"), + .target = target, + .optimize = optimize, + // 默认会链接 libc;需要链接系统库时可以在这里声明, + // 翻译时也会自动包含对应库的头文件 + // .link_system_libs = &.{.{ .name = "glfw3" }}, + }); + // 翻译时需要的额外头文件目录 + // c.addIncludePath(b.path("include")); + + const exe = b.addExecutable(.{ + .name = "app", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "c", .module = c.mod }, + }, + }), + }); + b.installArtifact(exe); +} +``` + +源码中的改动就很简单了: + +```zig +// 0.16.0 +const c = @cImport({ + @cDefine("_NO_CRT_STDIO_INLINE", "1"); + @cInclude("stdio.h"); +}); + +// 0.17.0 +const c = @import("c"); +``` + +有几点需要注意: + +- `@cDefine`、`@cUndef` 对应头文件里的 `#define` / `#undef`;也可以使用 `Translator` 的 `defineCMacro` 等方法 +- 原来写在 `@cImport` 里的多个 `@cInclude`,可以合并到同一个头文件中,一次性翻译成一个模块 +- `zig translate-c` 命令行子命令依然可以用来查看翻译结果 + +### `@intFromEnum` / `@enumFromInt` 改为 `@backingInt` / `@fromBackingInt` + +`@intFromEnum` 和 `@enumFromInt` 被标记为 deprecated,取而代之的是 `@backingInt` 和 `@fromBackingInt`。**`zig fmt` 会自动完成这一替换**。由于 `@fromBackingInt` 的参数必须**恰好**是枚举的底层整数类型,不再接受其他整数类型,`zig fmt` 会把 `@enumFromInt(x)` 改写为 `@fromBackingInt(@intCast(x))`;手写新代码时同样需要注意这一点: + +```zig +const Color = enum(u4) { red, green, blue = 8 }; + +const n: usize = 8; + +// 0.16.0 +const c: Color = @enumFromInt(n); +const i = @intFromEnum(c); + +// 0.17.0 +const c: Color = @fromBackingInt(@intCast(n)); +const i = @backingInt(c); // u4 +``` + +另外: + +- `@backingInt` 也可以用于显式指定了底层整数类型的 `packed struct` / `packed union`,以及带标记的联合类型(返回当前激活标记的底层整数) +- 可以用 `std.meta.BackingInt(T)` 获取 `@backingInt` 的结果类型 +- 空枚举现在必须以 `noreturn` 作为底层类型:`const Empty = enum(noreturn) {};` +- 当 `@bitCast` 的目标是枚举类型时,现在也会对无效的标记值进行安全检查 + +### `@bitCast` 对数组 / 向量的语义与端序无关 + +这是本次升级中**最需要人工审查**的一项,因为它可能在没有任何编译错误的情况下改变程序行为。 + +`@bitCast` 现在作用于值的**逻辑位表示**:整数从最低有效位开始;数组和向量则从第一个元素开始,依次拼接各元素的位。结果与目标的端序无关: + +```zig +const bytes: [2]u8 = .{ 0x34, 0x12 }; +const x: u16 = @bitCast(bytes); +// 0.17.0 中,无论大端还是小端,x 都等于 0x1234 +// 0.16.0 中,大端目标上的结果是 0x3412 +``` + +在小端目标上,新行为与旧行为基本一致;但如果你的代码运行在大端目标上,或者原本就依赖“按内存布局重新解释”的语义,就需要修改。根据实际意图,可以选择: + +```zig +// 1. 需要明确的字节序时,使用 std.mem.readInt / writeInt +const le = std.mem.readInt(u16, &bytes, .little); +const be = std.mem.readInt(u16, &bytes, .big); + +// 2. 需要“按内存布局”重新解释时,使用 @ptrCast +const int_ptr: *align(1) const u16 = @ptrCast(&bytes); +const native = int_ptr.*; +``` + +此外,`@bitCast` **不再允许用于 `extern struct` / `extern union`**。这类代码本质上是在做 type punning,请改用 `@ptrCast`、`extern union`,或者借助 `std.mem.toBytes` / `std.mem.bytesToValue`。 + +### `@hasDecl` 只对 `pub` 声明返回 `true` + +以前,`@hasDecl` 对“同一文件中的非 `pub` 声明”也会返回 `true`,现在不再如此: + +```zig +const Foo = struct { + bar: i32, + + const baz = 1; + pub var quux = "xxx"; +}; + +test "@hasDecl example" { + try std.testing.expect(!@hasDecl(Foo, "bar")); + try std.testing.expect(!@hasDecl(Foo, "baz")); // 0.17.0 中变为 false + try std.testing.expect(@hasDecl(Foo, "quux")); +} +``` + +如果你在同一个文件里用 `@hasDecl` 检测私有声明(常见于根据可选声明切换实现的泛型代码),需要把被检测的声明标记为 `pub`。 + +### `internal` 与 `link_once` 链接属性被移除 + +`std.lang.GlobalLinkage` 中的 `.internal` 和 `.link_once` 被移除: + +- `.link_once` 的用途大多可以改用 `.weak` +- `.internal` 的替代方式是:一开始就不要 `@export` 这个符号 + +### 顺手可以用上的新特性 + +- `@divCeil`:向正无穷取整的整数除法,可以替换 `std.math.divCeil(a, b) catch unreachable` +- 长度在编译期已知的切片,现在可以直接解引用为数组(`slice.*`),或强制转换为数组指针 +- `@SpirvType`:在 SPIR-V 目标上声明 image、sampler 等类型 + +```zig +try expectEqual(2, @divCeil(5, 3)); +try expectEqual(-1, @divCeil(-5, 3)); + +const slice: []const u16 = &.{ 1, 2, 3 }; +const array: [3]u16 = slice.*; +const array_ptr: *const [3]u16 = slice; +``` + +## 标准库 + +### `std.builtin` 改名为 `std.lang` + +`std.builtin` 被标记为 deprecated,改为 `std.lang`。像 `std.builtin.Type`、`std.builtin.CallingConvention`、`std.builtin.Endian` 这样的写法,都可以直接替换为 `std.lang.Type`、`std.lang.CallingConvention`、`std.lang.Endian`。 + +### `OptimizeMode` 改为 `Optimize`,标签名去掉“release” + +`std.lang.OptimizeMode` 更名为 `std.lang.Optimize`,枚举标签也改成了小写且去掉了“release”: + +| 0.16.0 | 0.17.0 | +| :------------- | :------ | +| `Debug` | `debug` | +| `ReleaseSafe` | `safe` | +| `ReleaseFast` | `fast` | +| `ReleaseSmall` | `small` | + +虽然标准库提供了向后兼容的声明,但**与旧名称进行 `==` / `!=` 比较的代码将无法编译**,需要改写: + +```zig +const builtin = @import("builtin"); + +// 0.16.0 +if (builtin.mode == .Debug) {} + +// 0.17.0 +if (builtin.mode == .debug) {} +``` + +在 `build.zig` 中,函数签名里的类型也需要改名: + +```zig +// 0.16.0 +fn addExample(b: *std.Build, optimize: std.builtin.OptimizeMode) void {} + +// 0.17.0 +fn addExample(b: *std.Build, optimize: std.lang.Optimize) void {} +``` + +命令行参数也有了新名字:`-O fast`、`zig build -Doptimize=safe` 等。经测试,旧的 `-O ReleaseFast`、`-Doptimize=ReleaseFast` 目前仍然可以使用,但建议在脚本和 CI 中尽早改为新名称。 + +另外,推荐使用 `std.lang.Optimize.runtimeSafety` 来代替 `std.debug.runtime_safety`。 + +### `@import("builtin")` 中的冗余常量 + +`cpu`、`os`、`abi` 和 `object_format` 被标记为 deprecated,并将在 `0.18.0` 中移除: + +```zig +const builtin = @import("builtin"); + +// 0.16.0 +if (builtin.os.tag == .windows) {} + +// 0.17.0 +if (builtin.target.os.tag == .windows) {} +``` + +`object_format` 对应的是 `builtin.target.ofmt`。 + +### 类型反射改为“数组结构体”风格 + +这是本版本中改动面最广的一项。`@typeInfo` 返回的结构体、联合、枚举、函数等类型信息,不再是“由字段信息结构体组成的数组”,而是多个**并列的切片**。 + +**结构体**: + +```zig +// 0.16.0 +inline for (@typeInfo(S).@"struct".fields) |field| { + try s.fieldPrefix(field.name); + try printValue(field.type, @field(v, field.name)); +} + +// 0.17.0 +const info = @typeInfo(S).@"struct"; +inline for (info.field_names, info.field_types) |field_name, field_type| { + try s.fieldPrefix(field_name); + try printValue(field_type, @field(v, field_name)); +} +``` + +字段的默认值、对齐、是否为 `comptime` 等属性,被放进了与 `field_names` 等长的 `field_attrs` 中,例如 `info.field_attrs[i].default_value_ptr`。声明的名称则位于 `info.decl_names`。 + +**枚举**:`field_names` 与 `field_values` 两个等长切片: + +```zig +const e = @typeInfo(Color).@"enum"; +// e.field_names[2] => "blue" +// e.field_values[2] => 8 +``` + +**联合**:同样是 `field_names`、`field_types` 等并列切片。 + +**函数**:参数类型与参数属性被拆分开,调用约定、可变参数等则移进了 `attrs`: + +```zig +const f = @typeInfo(@TypeOf(add)).@"fn"; +// 0.16.0: f.params[0].type.?、f.params[1].is_noalias、f.calling_convention +// 0.17.0: +_ = f.param_types[0].?; +_ = f.param_attrs[1].@"noalias"; +_ = f.attrs.@"callconv"; +_ = f.return_type.?; +``` + +**指针**:`is_const`、`is_volatile`、`alignment`、`address_space`、`is_allowzero` 等字段被收进了 `attrs`,其中对齐值变为可选值——`null` 表示使用子类型的自然对齐: + +```zig +const p = @typeInfo(*const align(8) u32).pointer; +// 0.16.0: p.is_const、p.alignment +// 0.17.0: +_ = p.attrs.@"const"; +_ = p.attrs.@"align".?; +``` + +**错误集**:变为 `error_names: ?[]const [:0]const u8`,`null` 表示 `anyerror`。 + +与之相应,`std.meta.fieldNames`、`std.meta.fieldTypes`、`std.meta.fieldInfo` 也被标记为 deprecated,请直接使用 `@typeInfo(T).@"struct".field_names` 等字段。 + +::: tip 🅿️ 提示 + +`0.16.0` 中引入的 `@Struct`、`@Union`、`@Enum`、`@Fn`、`@Pointer` 等类型构造内建函数,本来就采用“名字数组 + 类型数组 + 属性数组”的形式传参。`0.17.0` 让 `@typeInfo` 的返回值也变成了同样的形式,因此“读取类型信息 → 修改 → 重新构造类型”的代码会比以前顺畅得多。 + +::: + +### `DebugAllocator` 改为 `SafeAllocator` + +`std.heap.DebugAllocator` 与 `std.heap.Check` 被标记为 deprecated,替代品是线程安全的 `std.heap.SafeAllocator`。它不再是泛型类型,配置通过 `init` 的参数传入,并且需要显式提供后备分配器: + +```zig +// 0.16.0 +var debug_allocator: std.heap.DebugAllocator(.{}) = .init; +defer if (debug_allocator.deinit() == .leak) @panic("memory leak"); +const gpa = debug_allocator.allocator(); + +// 0.17.0 +var safe_allocator: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); +defer if (safe_allocator.deinit() != 0) @panic("memory leak"); +const gpa = safe_allocator.allocator(); +``` + +注意 `deinit` 的返回值从 `Check` 枚举变成了**泄漏的数量**(`usize`)。原来的 `DebugAllocatorConfig` 对应 `SafeAllocator.Options`,可配置栈追踪帧数、canary 值等。 + +当然,如果你在 `0.16.0` 中已经改用了 `pub fn main(init: std.process.Init)`,直接使用 `init.gpa` 即可,不需要自己创建分配器。 + +### `stackFallback` 改为 `BufferFirstAllocator` + +“先用栈上缓冲区、不够再回退到堆”的分配器被重做了。现在缓冲区由调用者传入,分配器不再对缓冲区大小泛型,也可以自由控制缓冲区的对齐。 + +需要注意,**发布说明里仍沿用了 `StackFallbackAllocator` 这个名字,但在 `0.17.0` 的标准库中,它的实际名称是 `std.heap.BufferFirstAllocator`**: + +```zig +// 0.16.0 +var stack align(@max( + @alignOf(std.heap.StackFallbackAllocator(0)), + @alignOf(Item), +)) = std.heap.stackFallback(@sizeOf(Item), gpa); +const allocator = stack.get(); + +// 0.17.0 +var stack_buf: [256]u8 = undefined; +var stack: std.heap.BufferFirstAllocator = .init(&stack_buf, gpa); +const allocator = stack.allocator(); +``` + +### `memory_pool` 的 managed 版本被移除 + +`std.heap.memory_pool.AlignedManaged` 与 `ExtraManaged` 被移除,请改用 `std.heap.memory_pool.Aligned` 与 `Extra`,并在调用时显式传入分配器。 + +### `fmt.allocPrint` 改为 `Allocator.print` + +```zig +// 0.16.0 +const s = try std.fmt.allocPrint(gpa, "{s}={d}", .{ x, y }); + +// 0.17.0 +const s = try gpa.print("{s}={d}", .{ x, y }); +``` + +`std.fmt.allocPrintSentinel` 对应 `Allocator.printSentinel`。旧函数目前仍然可用,但已被标记为 deprecated。 + +### `ArrayList` 的 `getLast` 系列 + +```zig +// 0.16.0 +if (list.getLastOrNull()) |foo| { + // ... +} +const foo = list.getLast(); + +// 0.17.0 +if (list.last()) |foo| { + // ... +} +const foo = list.last().?; +``` + +新增的 `lastPtr` 返回 `?*T`,可以用来原地修改最后一个元素。 + +### `std.zon.parse` 重做 + +`std.zon.parse` 中的解析函数现在接受一个选项结构体作为参数,结果从 arena 中分配,因此 `std.zon.parse.free` 也被移除了: + +```zig +const Diagnostics = std.zon.parse.Diagnostics; + +// 0.16.0 +var diag: Diagnostics = .{}; +defer diag.deinit(gpa); +const result = std.zon.parse.fromSliceAlloc(MyZonType, gpa, source, &diag, .{}) catch |err| switch (err) { + error.ParseZon => std.process.fatal("input.zon: {f}", .{diag}), + error.OutOfMemory => |e| return e, +}; +defer std.zon.parse.free(gpa, result); + +// 0.17.0 +var diag: Diagnostics = undefined; +const result = std.zon.parse.fromSlice(MyZonType, .{ + .gpa = gpa, + .arena = arena, + .source = source, + .diagnostics = &diag, +}) catch |err| switch (err) { + error.ParseZon => diag.fatal("input.zon"), + error.OutOfMemory => |e| return e, +}; +``` + +另外请留意方法名的变化:原来的 `fromSliceAlloc` 改名为 `fromSlice`,而原来**不分配内存**的 `fromSlice` 改名为 `fromSliceNoAlloc`,`fromZoir` 等其他“from”系列方法也按同样的规则改名。 + +注意发布说明中把这些函数简写成了 `std.zon.fromSlice`,但 `std.zon` 本身并没有导出它们,实际仍需通过 `std.zon.parse.fromSlice` 调用。 + +### `bit_set` 类型与 `initEmpty` / `initFull` + +| 0.16.0 | 0.17.0 | +| :----------------------------- | :----------------------------------------- | +| `std.bit_set.IntegerBitSet` | `std.bit_set.Integer` | +| `std.bit_set.ArrayBitSet` | `std.bit_set.Array` | +| `std.StaticBitSet` | `std.bit_set.Static` | +| `std.DynamicBitSetUnmanaged` | `std.bit_set.Dynamic` | +| `std.DynamicBitSet` | `std.bit_set.DynamicManaged`(deprecated) | +| `.initEmpty()` / `.initFull()` | `.empty` / `.full` | + +`std.enums.EnumSet` 的 `initEmpty` / `initFull` 也同样改为 `empty` / `full`: + +```zig +// 0.16.0 +var set = std.bit_set.IntegerBitSet(8).initEmpty(); + +// 0.17.0 +var set: std.bit_set.Integer(8) = .empty; +``` + +### `Uri.getHost` 改为 `HostName.fromUri` + +```zig +var host_buf: [HostName.max_len]u8 = undefined; + +// 0.16.0 +const host = try uri.getHost(&host_buf); + +// 0.17.0 +// 注意错误集与之前不同,因为 fromUri 会进行校验 +const host = try HostName.fromUri(uri, &host_buf); +``` + +`Uri.getHostAlloc` 被直接移除。由于语义差异较大,这里没有提供平滑的弃用过渡,需要逐一评估调用处。 + +### 其他需要顺手处理的改名 + +| 0.16.0 | 0.17.0 | +| :------------------------------------------------------- | :------------------------------ | +| `std.gpu` | `std.spirv` | +| `std.DoublyLinkedList.pop` | `std.DoublyLinkedList.popLast` | +| `std.ascii.indexOfIgnoreCase` 系列 | `std.ascii.findIgnoreCase` 系列 | +| `std.mem.containsAtLeastScalar2` | `std.mem.containsAtLeastScalar` | +| `std.mem.readPackedIntNative` / `readPackedIntForeign` | `std.mem.readPackedInt` | +| `std.mem.writePackedIntNative` / `writePackedIntForeign` | `std.mem.writePackedInt` | +| `std.Target.parseCpuModel` 返回错误 | 返回可选值 | + +还有一处行为变化:`std.mem.eql` 与 `std.mem.findDiff` 在比较**浮点切片**时,不再因为“两个切片指向同一块内存”而直接返回相等,因此包含 `nan` 的同一个切片与自身比较时会返回 `false`。 + +## 构建系统 + +`0.17.0` 的构建系统被拆分为两个进程:**配置进程(configurer)**负责运行你的 `build.zig` 并生成构建图,**执行进程(maker)**负责包管理和执行构建图。配置结果会被缓存,在配置没有变化时,`zig build` 可以完全跳过 `build.zig` 的执行。 + +这带来了两条新的基本规则: + +1. **`build` 函数里不应该再有副作用**,例如直接 spawn 子进程、写文件;需要在构建过程中做的事情,都应该声明为构建步骤 +2. **配置逻辑读取了哪些外部状态,就需要显式声明**,否则配置缓存可能无法及时失效 + +### `b.build_root` 改为 `b.root` + +`b.build_root`(`Directory`)被移除,改为 `b.root`,类型是 `Cache.Path`。如果需要在配置阶段遍历项目中的目录: + +```zig +// 0.16.0 +const full_path = try std.process.currentPathAlloc(io, b.allocator); +var dir = try std.Io.Dir.openDirAbsolute(io, full_path, .{ .iterate = true }); + +// 0.17.0 +const io = b.graph.io; +// 配置逻辑依赖该目录中的条目,需要显式声明, +// 这样新增、删除或重命名文件后才会重新执行配置 +b.dependOnDirectoryContents(b.path("examples")); +var dir = try b.root.openDir(io, "examples", .{ .iterate = true }); +defer dir.close(io); +``` + +### 在配置阶段声明外部依赖 + +与上面的 `dependOnDirectoryContents` 类似,构建系统提供了四个函数,用来声明配置逻辑依赖的外部状态: + +| 函数 | 何时使配置缓存失效 | +| :---------------------------- | :-------------------------------------- | +| `b.dependOnFileContents` | 文件内容发生变化 | +| `b.dependOnFileMetadata` | 文件的大小、inode、mtime 或内容发生变化 | +| `b.dependOnDirectoryContents` | 目录中有条目被添加、删除或重命名 | +| `b.dependOnDirectoryMetadata` | 目录的最后修改时间发生变化 | + +而像 `b.findProgram` 这类无法被精确追踪的操作,或者直接调用 `b.graph.poisonCache()`,会让配置缓存“被污染”:这样仍然能得到正确的结果,只是每次都需要重新执行 `build.zig`。 + +如果想确认自己的构建脚本是否“纯净”,可以使用 `zig build --cache-poison=disallowed`:一旦配置缓存将被污染,构建就会直接 panic,方便定位问题。 + +### 不要在 `build` 函数里直接执行命令 + +过去有些构建脚本会在 `build` 函数中直接用 `std.process.spawn` 等方式运行命令(例如依次构建子项目)。在新的模型下,应当把这些操作声明为 `Run` 步骤,交给执行进程去完成: + +```zig +// 0.16.0:在配置阶段直接 spawn 子进程 +var child = try std.process.spawn(io, .{ + .argv = &.{ "zig", "build" }, + .cwd = .{ .path = sub_dir }, +}); +_ = try child.wait(io); + +// 0.17.0:声明为 Run 步骤,在执行阶段运行 +const sub_build = b.addSystemCommand(&.{ b.graph.zig_exe, "build" }); +sub_build.setName("zig build (sub project)"); +sub_build.setCwd(b.path("sub_project")); +sub_build.stdio = .inherit; +b.getInstallStep().dependOn(&sub_build.step); +``` + +### `b.args` 改为 `run.addPassthruArgs()` + +`b.args` 被移除了。构建脚本不再能在配置阶段读到 `zig build run -- arg1 arg2` 中 `--` 之后的参数,而是声明一个“透传参数”的占位,由执行进程在运行时替换。作为交换,修改这些参数时不再需要重新执行构建脚本: + +```zig +const run_cmd = b.addRunArtifact(exe); + +// 0.16.0 +if (b.args) |args| { + run_cmd.addArgs(args); +} + +// 0.17.0 +run_cmd.addPassthruArgs(); +``` + +### `Run` 步骤的参数方法统一为 `...Arg2` + +`Run` 步骤中原来成对出现的 `addXxxArg` / `addPrefixedXxxArg` 被统一为带选项结构体的 `...Arg2` 版本,旧方法被标记为 deprecated: + +```zig +// 0.16.0 +run.addArtifactArg(exe); +run.addPrefixedFileArg("--input=", b.path("data.txt")); +const out = run.addPrefixedOutputFileArg("-o", "out.bin"); + +// 0.17.0 +run.addArtifactArg2(exe, .{}); +run.addFileArg2(b.path("data.txt"), .{ .prefix = "--input=" }); +const out = run.addOutputFileArg2("out.bin", .{ .prefix = "-o" }); +``` + +路径类参数的选项中还有 `suffix` 和 `make_absolute`(把路径转为绝对路径后再传给子进程)。其他方法同理:`addDirectoryArg2`、`addOutputDirectoryArg2`、`addFileContentArg2`、`addDepFileOutputArg2`。 + +### `findProgram` 与 `findProgramLazy` + +`b.findProgram` 的签名发生了变化,并且新增了不会污染配置缓存的 `findProgramLazy`: + +```zig +// 0.16.0 +const python = try b.findProgram(&.{ "python3", "python" }, &.{}); + +// 0.17.0:在配置阶段立即查找,找不到返回 null,会污染配置缓存 +const python = b.findProgram(.{ .names = &.{ "python3", "python" } }); + +// 0.17.0:返回 LazyPath,只有在被某个步骤用到时才会真正查找 +const python_lazy = b.findProgramLazy(.{ .names = &.{ "python3", "python" } }); +``` + +如果配置逻辑并不需要知道“程序是否存在”,只是要在某个步骤里调用它,优先使用 `findProgramLazy`。 + +### 其他构建 API 的变化 + +- `LazyPath.getDisplayName()` 改为通过 `"{f}"` 格式化打印 `LazyPath` +- `LazyPath.basename` 被移除,因为该值在执行阶段之前是未知的 +- `ConfigHeader.Options` 中的 `include_guard_override` 改为 `include_guard`;另外 `ConfigHeader` 现在对所有风格都会报告未使用的值 +- `Fmt` 步骤的 `paths` / `exclude_paths` 现在是 `LazyPath` 列表,可以使用 `b.pathList(&.{ "src", "build.zig" })` 创建 +- `Step.Options` 添加路径时需要显式选择 `addOptionPath`(文件)、`addOptionPathDirectory`(目录)或 `addOptionPathUntracked`(不追踪) +- `b.dependency` 现在也支持惰性依赖;新增的 `b.dependencyLazy` 返回 `error{LazyDependencyNeeded}!*Dependency`,可以配合 `try` 使用 +- 覆盖 build runner 的能力被移除;需要读取构建图的工具,可以使用 `zig build --print-configuration` 或 Build Server Protocol(`--listen=-`) +- Windows 资源相关的 API(如 `Module.addWin32ResourceFile`)被标记为 deprecated,将在下一个版本移到独立的包中 +- 以 Windows 或 Wine 为目标时,`Run` 步骤只会根据 `argv[0]` 的 DLL 依赖修改 `PATH`,而不再对所有 artifact 参数这样处理 + +### 包管理 + +- `zig fetch ` 现在只抓取到全局缓存;只有使用 `--save` 时才会同时抓取到项目本地的包目录(默认是 `zig-pkg`),全局抓取时也不再要求存在 `build.zig` +- `zig build` 总是会把依赖抓取到项目本地(同时也会抓取到全局缓存) +- `--pkg-path` 参数与 `ZIG_LOCAL_PKG_DIR` 环境变量现在对 fetch 和 build 命令都生效 +- 修复了路径依赖可以逃逸出父包根目录的 bug,如果你的项目依赖了这种行为,需要调整依赖布局 + +### 增量编译 + +在 `x86_64-linux` 上,现在大多数项目都可以通过 `zig build -fincremental --watch` 使用增量编译,修改源码后几乎可以立即完成重新构建,值得一试。 + +## 工具链与环境 + +- **macOS 的最低版本要求提升到了 15.0**,DragonFly BSD 提升到了 6.4。如果你的 CI 使用较旧的 macOS runner,需要相应升级 +- `libc.txt` 中的 `gcc_dir` 字段更名为 `cc_dir`,并且在 Linux 目标上变为必填项 +- 移除了 `powerpc-linux-gnueabi[hf]` 与 `powerpc64-linux-gnu` 目标 +- `x86_64-macos`、`x86-windows` 等目标被标记为“过时”,后续版本可能移除支持 +- 官方列出的已知回归中,#36444 会影响在 `Run` 步骤中使用响应文件(response file)的场景,如果你的构建依赖这种用法,升级前请先评估 + +## 小结 + +总体来说,`0.17.0` 的迁移可以分为三块: + +- **语法与内建函数**:大部分是机械替换,`zig fmt` 能自动处理一部分,`errdefer` 捕获需要拆函数 +- **反射与标准库**:类型反射的数组结构体风格是改动最集中的部分,分配器、`bit_set`、`zon` 等 API 按编译错误逐个处理即可 +- **构建系统**:理解“配置与执行分离、配置可缓存”这一新模型之后,`b.build_root`、`b.args`、在 `build` 函数中执行命令等问题都会迎刃而解 + +完成迁移之后,你会得到一个更快、更易缓存的构建流程,以及一门正在加速走向稳定的语言。