diff --git a/content/post/2026-04-16-zig-first-impressions.smd b/content/post/2026-04-16-zig-first-impressions.smd new file mode 100644 index 0000000..7d33300 --- /dev/null +++ b/content/post/2026-04-16-zig-first-impressions.smd @@ -0,0 +1,282 @@ +--- +.title = "Zig 初体验", +.date = @date("2026-04-16T10:00:00+0800"), +.author = "copilot (翻译)", +.layout = "post.shtml", +.draft = false, +.custom = { + .ai_generated = true, +}, +--- + +> 原文:https://dev.to/nw229/zig-first-impressions-3f5p + +## [前言]($heading.id('preface')) + +最近我一直在学习 Zig。作为一名使用 C、C++ 和 Rust 多年的开发者,每当一门新的系统编程语言出现,我都会忍不住去试一试。Zig 承诺提供 C 级别的性能,同时拥有比 C 更好的安全性,以及比 Rust 更简单的学习曲线。这些听起来很有吸引力,于是我花了一些时间亲自体验了一番。 + +## [什么是 Zig?]($heading.id('what-is-zig')) + +Zig 是一门系统级编程语言,由 Andrew Kelley 创建,目标是成为 C 的现代替代品。它的核心设计理念包括: + +- **没有隐藏的控制流**:没有运算符重载,没有异常,没有析构函数——你看到什么,执行的就是什么 +- **没有隐藏的内存分配**:标准库的函数不会在你不知情的情况下进行堆分配 +- **可选类型取代 null**:消除了整类空指针解引用的 bug +- **错误作为值**:错误处理被集成进类型系统,不再是事后补丁 + +## [安装与工具链]($heading.id('installation')) + +安装 Zig 出乎意料地简单。从[官网](https://ziglang.org/download/)下载对应平台的压缩包,解压,把 `zig` 二进制文件加入 `PATH` 即可。整个过程不超过五分钟。 + +```bash +# 验证安装 +zig version +# 0.14.0 +``` + +这和安装 Rust(需要 rustup、cargo 等)相比,简洁许多。`zig` 这个二进制文件本身就包含了编译器、构建系统、包管理器和格式化工具,是一个真正的"全家桶"。 + +## [Hello, World!]($heading.id('hello-world')) + +```zig +const std = @import("std"); + +pub fn main() void { + std.debug.print("Hello, World!\n", .{}); +} +``` + +用 `zig run hello.zig` 运行。第一次会编译,后续有缓存,速度很快。 + +第一个让我感到不适应的地方:`std.debug.print` 的第二个参数是一个匿名结构体字面量 `.{}`,用于传递格式参数。这种写法在 Zig 中随处可见,一开始觉得奇怪,习惯后感觉很统一。 + +## [语法特点]($heading.id('syntax')) + +### 类型系统 + +Zig 是强静态类型语言,没有任何隐式类型转换。所有转换必须显式: + +```zig +const x: i32 = 42; +const y: i64 = x; // 编译错误! +const z: i64 = @intCast(x); // 正确:显式转换 +``` + +这初看是负担,实际上消除了一大类因为隐式转换引发的 bug,尤其是整数截断。 + +### 可选类型与空值安全 + +Zig 没有 `null` 关键字,取而代之的是可选类型 `?T`: + +```zig +fn findUser(id: u32) ?User { + if (id == 0) return null; + return User{ .id = id }; +} + +const user = findUser(42) orelse return; +// 或者 +if (findUser(42)) |u| { + std.debug.print("Found: {}\n", .{u.id}); +} +``` + +编译器强制你在使用可选值之前处理 null 情况,这从根源上杜绝了空指针解引用。 + +### defer + +`defer` 关键字让资源清理变得优雅: + +```zig +const file = try std.fs.cwd().openFile("data.txt", .{}); +defer file.close(); // 函数返回时自动执行,无论是否出错 + +const data = try file.readToEndAlloc(allocator, 1024 * 1024); +defer allocator.free(data); +``` + +和 Go 的 `defer` 类似,但 Zig 的 `defer` 是按照 LIFO 顺序在当前块(block)结束时执行,而不是函数结束时——这个区别很重要。 + +## [错误处理]($heading.id('error-handling')) + +这是我最喜欢 Zig 的地方之一。错误是值,有自己的类型: + +```zig +const MyError = error{ + OutOfMemory, + InvalidInput, + Overflow, +}; + +fn divide(a: i32, b: i32) MyError!i32 { + if (b == 0) return error.InvalidInput; + return @divExact(a, b); +} + +pub fn main() !void { + const result = try divide(10, 2); + std.debug.print("Result: {d}\n", .{result}); + + // 处理特定错误 + const r2 = divide(10, 0) catch |err| switch (err) { + error.InvalidInput => { + std.debug.print("Cannot divide by zero\n", .{}); + return; + }, + else => return err, + }; + _ = r2; +} +``` + +`try` 是 `catch |err| return err` 的语法糖,让错误向上传播变得简洁。`catch` 则可以捕获并处理错误。 + +与 Go 相比,不需要写烦人的 `if err != nil`;与 Rust 相比,也不需要 `?` 运算符和大量 `match`——两者取了个折中,但我觉得 Zig 的方案更直观。 + +## [内存管理:分配器(Allocator)]($heading.id('allocator')) + +Zig 最独特的设计之一是显式分配器系统。任何可能进行堆分配的函数,都需要你传入一个分配器: + +```zig +const std = @import("std"); + +pub fn main() !void { + // 通用分配器,会在 deinit 时检测内存泄漏 + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + // 分配一个动态数组 + var list = std.ArrayList(i32).init(allocator); + defer list.deinit(); + + try list.append(1); + try list.append(2); + try list.append(3); + + for (list.items) |item| { + std.debug.print("{d}\n", .{item}); + } +} +``` + +这种设计的好处: + +1. **测试友好**:测试时使用 `std.testing.allocator`,会自动检测内存泄漏并报告 +2. **策略灵活**:可以根据场景选择分配器,比如用 `ArenaAllocator` 批量分配后一次性释放 +3. **零全局状态**:不依赖全局堆,代码更容易推理 + +起初觉得到处传分配器很繁琐,但用久了反而觉得这种"明确化"非常有价值——你始终清楚地知道哪里在分配内存。 + +## [comptime:编译时计算]($heading.id('comptime')) + +`comptime` 是 Zig 最具魔力的特性。它允许在编译期执行任意 Zig 代码,实现泛型、类型计算、条件编译等: + +```zig +// 泛型函数:类型是 comptime 参数 +fn max(comptime T: type, a: T, b: T) T { + return if (a > b) a else b; +} + +pub fn main() void { + std.debug.print("{d}\n", .{max(i32, 3, 7)}); + std.debug.print("{d}\n", .{max(f64, 3.14, 2.71)}); +} +``` + +更强大的用法——在编译期生成类型: + +```zig +fn Queue(comptime T: type) type { + return struct { + items: std.ArrayList(T), + + pub fn init(allocator: std.mem.Allocator) @This() { + return .{ .items = std.ArrayList(T).init(allocator) }; + } + + pub fn push(self: *@This(), item: T) !void { + try self.items.append(item); + } + + pub fn pop(self: *@This()) ?T { + if (self.items.items.len == 0) return null; + return self.items.orderedRemove(0); + } + }; +} +``` + +这比 C++ 模板更直观,也不需要 Rust 的 trait 系统就能实现完整的泛型。Zig 里类型是一等公民,可以像值一样传递和计算。 + +## [构建系统]($heading.id('build-system')) + +Zig 的构建系统通过 `build.zig` 文件定义,本身就是 Zig 代码: + +```zig +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "my-app", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + b.installArtifact(exe); + + const run = b.addRunArtifact(exe); + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run.step); +} +``` + +内置支持交叉编译,只需传入 `-Dtarget=` 参数。无需 Docker、无需额外工具链,一台机器就可以为所有平台构建。 + +## [与 C 的互操作]($heading.id('c-interop')) + +Zig 可以直接导入 C 头文件,这是其核心特性之一: + +```zig +const c = @cImport({ + @cInclude("stdio.h"); +}); + +pub fn main() void { + _ = c.printf("Hello from C API!\n"); +} +``` + +`zig cc` 还可以直接当作 C/C++ 编译器使用,支持 musl 静态链接,可以生成真正的"单文件可执行"。 + +## [初体验总结]($heading.id('summary')) + +经过几周的摸索,我对 Zig 的整体印象是:**这是一门设计非常有想法的语言,哲学上的一致性令人印象深刻。** + +**喜欢的地方:** + +- 没有隐藏行为,代码读起来所见即所得 +- 错误处理优雅,既明确又不繁琐 +- `comptime` 系统强大而简洁,没有 C++ 模板的复杂性 +- 工具链一体化,无需折腾各种外部工具 +- 与 C 的互操作是一等公民,不是事后补丁 + +**需要适应的地方:** + +- 语言仍在开发中(未到 1.0),API 时常变化 +- 生态系统相对较小,很多需求要直接调 C 库 +- `comptime` 的心智模型需要时间建立 +- 文档不够完善,有时需要直接阅读标准库源码 + +**适合 Zig 的场景:** + +- 系统编程、嵌入式开发 +- 需要与现有 C 代码库深度集成 +- 对可执行文件大小和启动性能敏感的场景 +- 作为 C 的现代替代品,但不想引入 Rust 的复杂性 + +总的来说,Zig 值得每一个系统程序员认真了解。它在"给程序员完整控制权"和"避免 C 的历史包袱"之间找到了一个有趣的平衡点。我打算继续深入学习,看看它是否能成为我工具箱里的常规工具。