Ziglings 笔记 62: 循环也是表达式

循环的返回值

在 Ziglings 的第 62 个练习中,我们解锁了循环的新玩法。 在此之前,forwhile 只是用来重复执行代码的。但就像 if/else 可以作为表达式一样,Zig 的循环也可以有返回值。

挑战:查找并赋值

我们需要在编程语言列表 langs 中寻找第一个名字长度为 3 的语言(即 “Zig”)。

  • 如果找到了,把它赋值给 current_lang
  • 如果找遍了整个数组都没找到,把 null 赋值给 current_lang

重要的是,我们要把 current_lang 声明为 const,这意味着我们不能使用“先声明 var 再在循环里赋值”的老办法。

解决方案

我们需要利用 for 循环的 else 分支:

const print = @import("std").debug.print;

pub fn main() void {
    const langs: [6][]const u8 = .{
        "Erlang", "Algol", "C", "OCaml", "Zig", "Prolog",
    };

    // 核心代码:循环表达式
    // 这里的 for 循环整体就是一个表达式,它的结果被赋给了 current_lang
    const current_lang: ?[]const u8 = for (langs) |lang| {
        // 情况 1: 找到了
        // 使用 break 携带一个值跳出循环,这个值就是循环的返回值
        if (lang.len == 3) break lang;
        
    } else null; // 情况 2: 没找到 (循环自然结束)
                 // 必须提供 else 分支作为默认返回值

    if (current_lang) |cl| {
        print("Current language: {s}\n", .{cl});
    } else {
        print("Did not find a three-letter language name. :-(\n", .{});
    }
}

核心知识点总结

1. break 带值

我们习惯了 break 只是单纯地跳出循环。但在 Zig 中,break 后面可以跟一个表达式,就像函数里的 return 一样。

2. 循环的 else

很多人看到循环后面跟个 else 会觉得奇怪(Python 也有这个特性,但很少用)。 在 Zig 中,当循环作为表达式时,else必须的。它处理的是“循环跑完了但没触发 break”的情况。

  • break 返回一种可能的值。
  • else 返回另一种可能的值(通常是默认值或 null)。 这两个值的类型必须是兼容的。

3. 为什么要这样设计?

为了不可变性 (Immutability)。 这种写法允许我们将 current_lang 定义为 const。不可变的变量更容易推理,也更不容易出 Bug。这消除了传统写法中 var found = null; ... found = x; 的状态变更过程。


后续预告:我们已经掌握了基本的控制流和数据类型。在进入更高级的编译期编程之前,我们还需要填补一些基础知识的拼图。比如,如何给代码块命名并在嵌套循环中跳转?下一篇我们将学习 Labeled Blocks。