Ziglings 笔记 26: 真正的 Hello World
这次是真的
在 Ziglings 的第 26 个练习中,我重写了 Hello World。
你可能会问:“为什么?std.debug.print 不好用吗?”
答案是:std.debug.print 是给程序员看的(输出到 stderr,忽略错误),而 stdout 是给用户看的(输出到 stdout,必须处理错误)。
如果我要写一个像 ls 或 grep 这样的命令行工具,必须使用标准输出。
挑战:可能会失败的打印
在系统编程中,一切都可能失败。写入终端可能会因为管道断开(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。