Ziglings 笔记 26: 真正的 Hello World

这次是真的

在 Ziglings 的第 26 个练习中,我重写了 Hello World。

你可能会问:“为什么?std.debug.print 不好用吗?” 答案是:std.debug.print 是给程序员看的(输出到 stderr,忽略错误),而 stdout 是给用户看的(输出到 stdout,必须处理错误)。

如果我要写一个像 lsgrep 这样的命令行工具,必须使用标准输出。

挑战:可能会失败的打印

在系统编程中,一切都可能失败。写入终端可能会因为管道断开(Broken Pipe)而失败。Zig 强迫我们要么处理这个错误,要么显式地忽略它。

解决方案

这是生产环境级别的 Hello World:

const std = @import("std");

// 1. 返回类型改为 !void
// 这让 main 函数可以返回错误。Zig 会自动推导(Infer)错误的类型。
pub fn main() !void {
    // 2. 获取标准输出流
    // std.fs.File.stdout() 是获取 stdout 句柄的标准方式
    var stdout = std.fs.File.stdout().writer(&.{});

    // 3. 使用 try 处理 I/O 错误
    // 如果打印失败,try 会立即停止程序并将错误返回给操作系统。
    // 这比直接崩溃要优雅得多。
    try stdout.interface.print("Hello world!\n", .{});
}

核心知识点总结

1. !void 和推导错误集

对于 main 函数,Zig 允许使用 !void 来表示“可能返回任何错误”。这对于应用程序的入口点非常方便,因为我们不需要手动列出所有可能发生的 I/O 错误。

2. 标准输出 vs 调试输出

  • std.debug.print:
    • 输出到 stderr (标准错误流)。
    • 吞掉错误 (不会崩溃,也不会报错)。
    • 用途:开发调试。
  • stdout.interface.print:
    • 输出到 stdout (标准输出流)。
    • 返回错误 (必须处理)。
    • 用途:正常的程序输出。

3. try 的终极形态

在这里,try 充当了错误守门员。如果 print 遇到问题(例如用户通过管道把输出重定向到一个已满的磁盘),程序会以非零状态码退出,这正是我们在编写健壮的 CLI 工具时所期望的行为。


后续预告:我们已经学会了如何优雅地处理错误(try)和退出(return)。但如果我们需要在函数退出前清理资源(比如关闭文件),无论函数是正常结束还是报错结束都要执行,该怎么办?下一篇我们将学习 defer