Skip to content

Commit 03ed3fc

Browse files
committed
new post: why zig uses cdw
1 parent 97db542 commit 03ed3fc

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
.title = "显式之美:从 `std.fs.cwd()` 看 Zig 的工程哲学",
3+
.date = @date("2026-01-13T15:54:31+0800"),
4+
.author = "jiacai2050",
5+
.layout = "post.shtml",
6+
.draft = false,
7+
---
8+
9+
在许多主流编程语言(如 C, Python, Go)中,访问文件通常是一个极其简单的动作:`open("file.txt")`。但在 Zig 中,你通常必须先通过 `std.fs.cwd()` 获取当前工作目录的句柄,再通过该句柄去打开文件。
10+
11+
这种设计初看起来增加了代码的“摩擦力”,但深究其背后,你会发现它精准地践行了 Zig 的核心设计理念:**显式、安全、不隐藏任何复杂性**。
12+
13+
---
14+
15+
## 1. 显式权力:能力导向安全 (Capability-based Security)
16+
17+
Zig 奉行**最小权限原则**。在传统语言中,任何一段代码(甚至是第三方库)只要能调用全局的 `open()`,就意味着它拥有了访问整个文件系统的潜在权力。
18+
19+
* **Zig 的做法:** 文件系统被抽象为 `Dir` 对象(目录句柄)。
20+
* **工程优势:** 如果你编写一个处理图像的库函数,你可以要求调用者传入一个 `Dir` 句柄。这意味着你的库函数**只能**看到并操作这个目录下的文件,它无法越权去读取用户的敏感文件(如 `~/.ssh/id_rsa`)。
21+
22+
---
23+
24+
## 2. 拒绝隐式全局状态:线程安全与确定性
25+
26+
在 POSIX 标准中,当前工作目录(CWD)是一个**进程级**的全局状态。在多线程环境下,这会引发严重的隐患。
27+
28+
* **隐式风险:** 如果线程 A 调用了 `chdir()` 更改了工作目录,线程 B 正在进行的相对路径操作会瞬间指向错误的位置。
29+
* **Zig 的解决方案:** `std.fs.cwd()` 返回的是一个指向当前目录的静态句柄。Zig 鼓励开发者在程序启动初期获取句柄,然后将其显式传递。即使进程的全局 CWD 发生变化,你持有的 `Dir` 句柄依然锁定在它原本指向的物理位置。
30+
31+
---
32+
33+
## 3. 跨平台的真实抽象 (Cross-platform Consistency)
34+
35+
不同操作系统的路径逻辑差异巨大:
36+
37+
* **Windows:** 依赖驱动器号(`C:\`)和反斜杠。
38+
* **Linux/Unix:** 统一的根目录(`/`)和正斜杠。
39+
* **WASI (WebAssembly):** 甚至没有根目录的概念,只有宿主环境预先“挂载”给模块的目录句柄。
40+
41+
通过强制从一个 `Dir` 句柄(如 `cwd()`)开始操作,Zig 抹平了这些差异。无论是在开发桌面应用还是编译为 WASI 模块,代码逻辑都是统一的:**起点句柄 + 相对路径**。
42+
43+
---
44+
45+
## 4. 路径穿越攻击的天然防线
46+
47+
路径穿越(Path Traversal)是常见的文件系统漏洞,攻击者通过 `../` 尝试跳出预设目录。
48+
49+
Zig 的 `Dir` 结构体方法(如 `dir.openFile(...)`)在设计上就偏向于在当前句柄的范围内进行解析。这种基于句柄的操作方式,比直接拼接不可信的字符串路径要安全得多,因为它从逻辑层面界定了操作的边界。
50+
51+
---
52+
53+
## 5. 资源生命周期的显式管理
54+
55+
在 Zig 中,`Dir` 往往需要显式关闭。这符合 Zig **“没有隐藏控制流、没有隐藏资源分配”** 的理念。
56+
57+
```zig
58+
const std = @import("std");
59+
60+
pub fn main() !void {
61+
// 1. 显式获取当前起点
62+
const cwd = std.fs.cwd();
63+
64+
// 2. 明确基于起点创建目录
65+
try cwd.makePath("output/logs");
66+
67+
// 3. 打开子目录句柄,生命周期清晰
68+
var log_dir = try cwd.openDir("output/logs", .{});
69+
defer log_dir.close();
70+
71+
const file = try log_dir.createFile("trace.txt", .{});
72+
defer file.close();
73+
}
74+
75+
```
76+
77+
---
78+
79+
## 总结:为什么要多写一行代码?
80+
81+
Zig 认为,**为了方便而隐藏复杂性,最终会导致维护的灾难。**
82+
83+
要求你先写 `cwd()` 的本质,是强制让你意识到:**文件系统操作不是在真空中进行的,它始终依赖于上下文、权限和环境。** 这种“摩擦力”在编写简单的单文件脚本时可能是负担,但在构建高性能、高可靠性的系统软件时,它是防止逻辑崩塌的最坚实保障。

0 commit comments

Comments
 (0)