Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
282 changes: 282 additions & 0 deletions content/post/2026-04-16-zig-first-impressions.smd
Original file line number Diff line number Diff line change
@@ -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 的历史包袱"之间找到了一个有趣的平衡点。我打算继续深入学习,看看它是否能成为我工具箱里的常规工具。