Ziglings 笔记 106: 文件系统操作 (File System)

与磁盘对话

在 Ziglings 的第 106 个练习中,我们不再局限于控制台输出,而是开始在硬盘上留下痕迹。

Zig 的 std.fs 模块提供了一套非常现代的文件操作 API。它鼓励使用 相对路径目录句柄,这比传统的绝对路径操作更安全、更高效。

挑战:创建并写入

我们需要编写一个程序,完成以下连贯动作:

  1. 在当前目录下创建一个名为 output 的文件夹。
  2. 如果文件夹已存在,不要报错,继续执行。
  3. 打开这个文件夹。
  4. 在里面创建一个名为 zigling.txt 的文件。
  5. 写入一句话 “It’s zigling time!”。
  6. 全程确保没有资源泄漏。

解决方案

这个练习展示了 Zig 错误处理和资源管理的完美结合:

const std = @import("std");

pub fn main() !void {
    // 1. 获取当前工作目录 (Current Working Directory)
    const cwd: std.fs.Dir = std.fs.cwd();

    // 2. 创建目录 (带错误处理)
    // makeDir 可能会失败。我们特别关注 PathAlreadyExists 错误。
    // 如果目录已存在,我们 switch 捕获它并什么都不做 (=> {})。
    // 如果是其他错误,我们将其返回 (=> return e)。
    cwd.makeDir("output") catch |e| switch (e) {
        error.PathAlreadyExists => {},
        else => return e,
    };

    // 3. 打开目录
    // 这里的 .{} 是 OpenDirOptions,使用默认值。
    // 打开目录属于可能失败的操作,且我们无法恢复,所以用 try。
    var output_dir: std.fs.Dir = try cwd.openDir("output", .{});
    
    // 关键点:defer close
    // 无论 main 函数如何结束,确保关闭目录句柄。
    defer output_dir.close();

    // 4. 创建文件
    // createFile 默认会截断文件(如果已存在则清空)。
    const file: std.fs.File = try output_dir.createFile("zigling.txt", .{});
    
    // 关键点:defer close
    // 文件句柄也是宝贵的资源,必须关闭。
    defer file.close();

    // 5. 写入数据
    const bytes_written = try file.write("It's zigling time!");
    
    std.debug.print("Successfully wrote {d} bytes.\n", .{bytes_written});
}

核心知识点总结

1. 为什么是 cwd

Zig 所有的文件操作通常都基于一个 Dir 实例。std.fs.cwd() 是起点。这种设计避免了使用全局路径,让程序更容易在受限环境(如 WASI 或沙盒)中运行。

2. catch 的精细控制

catch |e| switch (e) { ... }

这是 Zig 错误处理的经典模式。它允许我们只忽略特定的错误(如“文件已存在”),而不会意外吞掉其他重要的错误(如“磁盘已满”或“权限被拒绝”)。

3. 配置选项

createFile("name", .{}) 中,那个不起眼的 .{} 实际上非常强大。它是 std.fs.File.CreateFlags。如果你想以追加模式打开文件,或者想在打开后同时读取,你可以这样写:

// 示例:允许读取
try dir.createFile("file.txt", .{ .read = true });

后续预告:我们已经成功写入了文件。那么,如何把文件内容读出来呢?如何读取一个不知道大小的文件?下一篇,我们将结合 Allocator 和 Reader,学习文件的读取操作。