Ziglings 笔记 28: Defer 与提前返回

拒绝重复

在 Ziglings 的第 28 个练习中,我们进一步挖掘了 defer 的潜力。 上一节我们学到了 defer 遵循“后进先出”的执行顺序,而这一节展示了它在多重返回点场景下的杀手级应用。

挑战:多出口函数的收尾

我们有一个 printAnimal 函数,它需要在动物名字两边打印括号,变成 (Goat) 的样子。 棘手的是,这个函数内部有多个 if 判断,每个判断里都有一个 return。这意味着函数可能在任何一行突然结束。

如果不使用 defer,我们可能得这样写:

// ❌ 糟糕的写法:重复代码太多
if (animal == 'g') {
    std.debug.print("Goat", .{});
    std.debug.print(") ", .{}); // 必须记得写这行
    return;
}
if (animal == 'c') {
    std.debug.print("Cat", .{});
    std.debug.print(") ", .{}); // 这里也要写
    return;
}
// ...

解决方案

使用 defer,我们只需要写一次收尾逻辑:

const std = @import("std");

pub fn main() void {
    const animals = [_]u8{ 'g', 'c', 'd', 'd', 'g', 'z' };
    for (animals) |a| printAnimal(a);
    std.debug.print("done.\n", .{});
}

fn printAnimal(animal: u8) void {
    std.debug.print("(", .{});

    // ✅ 解决方案:
    // 这行代码就像一个守门员。
    // 无论函数是从下面的哪个 return 出去,还是运行到最后,
    // 在离开前都必须经过这里打印右括号。
    defer std.debug.print(") ", .{});

    if (animal == 'g') {
        std.debug.print("Goat", .{});
        return; // 触发 defer
    }
    if (animal == 'c') {
        std.debug.print("Cat", .{});
        return; // 触发 defer
    }
    if (animal == 'd') {
        std.debug.print("Dog", .{});
        return; // 触发 defer
    }

    std.debug.print("Unknown", .{});
    // 函数自然结束,也会触发 defer
}

核心知识点总结

1. DRY 原则 (Don’t Repeat Yourself)

defer 是践行 DRY 原则的神器。在 C 语言中,为了避免重复写清理代码,开发者经常使用 goto cleanup; 标签。Zig 的 defer 提供了更安全、更结构化的替代方案。

2. 执行保证

defer 的核心承诺是:只要代码块被进入,其绑定的 defer 就一定会在退出时被执行(除非发生断电等灾难性崩溃)。这使得它非常适合处理成对的操作,如 print("(") ... print(")")lock()unlock()


后续预告:如果我们在 defer 之后又遇到了错误怎么办?Zig 提供了一个专门处理这种情况的变体:errdefer。它只在发生错误时才执行。