Ziglings 笔记 50: “无”的四种写法

关于“空”的哲学

在 Ziglings 的第 50 个练习中,我们不得不面对来自克苏鲁神话的凝视,以及 Zig 语言中关于“空 (No Value)”的四种定义。

在很多语言中,null 经常被滥用。但在 Zig 中,不同的“空”有严格的区分。

挑战:填补虚空

代码引用了洛夫克拉夫特的名言,我们需要为不同的变量选择正确的“空值”来初始化它们。

解决方案

这是修复后的代码:

const std = @import("std");

const Err = error{Cthulhu};

// main 函数不返回错误,也不返回数值,所以是 void
pub fn main() void {
    // 1. undefined
    // 场景:我声明了变量,且马上就会给它赋值。
    // 在赋值前,我不关心它里面是什么垃圾数据。
    var first_line1: *const [16]u8 = undefined;
    first_line1 = "That is not dead";

    // 2. Error (错误值)
    // 场景:这是一个 Error Union 类型。
    // 我们用一个具体的错误 Err.Cthulhu 来初始化它。
    var first_line2: Err!*const [21]u8 = Err.Cthulhu;
    first_line2 = "which can eternal lie";

    // 注意这里打印格式 {!s},专门用于打印 Error Union
    std.debug.print("{s} {!s} / ", .{ first_line1, first_line2 });

    printSecondLine();
}

// 3. void
// 场景:函数只执行动作(打印),不产生结果。
fn printSecondLine() void {
    // 4. null
    // 场景:这是一个 Optional 类型 (?T)。
    // 初始状态为空,表示“当前没有引用任何字符串”。
    var second_line2: ?*const [18]u8 = null;
    second_line2 = "even death may die";

    std.debug.print("And with strange aeons {s}.\n", .{second_line2.?});
}

核心知识点总结

1. undefined:未初始化的内存

  • 什么时候用:性能敏感场景,或者为了满足“变量必须初始化”的语法规则但稍后立即赋值。
  • 危险性:极高。永远不要读取它。

2. null:可选的空

  • 什么时候用:数据结构中(如链表末尾),或者函数查找失败(如 findItem 返回 null)。
  • 安全性:高。类型系统强制检查。

3. error:失败的结果

  • 什么时候用:I/O 操作、解析失败等。
  • 区别null 是正常的空,error 是异常的空。

4. void:信息的黑洞

  • 什么时候用:函数返回值。
  • 本质:它是一个类型,而不是一个值。在运行时,它完全消失了,不占用任何字节。

后续预告:我们已经对 Zig 的类型系统有了深刻的理解。接下来的练习将把目光转向底层:数值是如何在内存中表示的?下一篇我们将探讨值 (Values) 和类型转换。