comprehensive-rust 控制流精讲:深入理解 Rust 的loop无限循环
【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust
loop是 Rust 中最直白的循环关键字:它无条件地无限重复循环体,直到遇到break显式退出。在 Google Android 团队维护的 Rust 课程仓库 comprehensive-rust 中,它是控制流三兄弟(while、loop、for)里唯一"永不自行终止"的构造,也是唯一能带返回值离开的循环。读完本文,你将掌握loop的完整语法、break/continue与标签的配合方式,以及它在服务器常驻、嵌入式 panic 处理、异步状态机等真实场景中的典型用法。
loop的基本形态:永远重复,直到break
课程的 loop.md 开门见山:loop语句只是永远循环,直到出现break。它不需要任何条件表达式,loop { ... }本身即完整语句。
课程给出的示例通过计数器演示了最基础用法:
fn main() { let mut i = 0; loop { i += 1; dbg!(i); if i > 100 { break; } } }逐行拆解这段代码,可以提炼出loop的核心要素:
- 循环体没有任何前置条件:与
while不同,loop在进入循环体之前不会检查任何布尔表达式,天然是"先做再说"的语义。 - 计数器需要
mut:变量i声明为let mut i = 0;,因为在循环体内执行i += 1会修改它。Rust 默认不可变的规则同样作用于循环计数器。 - 退出依赖
break:if i > 100 { break; }是唯一的出口。若删除这行,程序将无限打印递增的i,永不结束。 dbg!用于调试观察:dbg!(i)是标准库调试宏,会向 stderr 打印表达式及其值并原样返回该值。课程在 macros.md 中专门介绍过它,适合在讲课或调试时观察循环进度。
整个控制流专题在 loops.md 中明确定位了三种循环关键字的分工:while在条件为真时反复执行,for遍历范围或集合,而loop则用于"没有条件、纯粹靠break决定何时离开"的场景。
loop与while true的关系
课程<details>折叠块给出了一个重要等价关系:loop的工作方式就像一个while true循环。也就是说,下面两种写法行为一致:
// 写法一:loop loop { // 处理请求... break; // 必须显式退出 } // 写法二:while true(不推荐,Rust 编译器会建议改用 loop) let mut cond = true; while cond { // 处理请求... cond = false; // 或直接 break }两者都要求循环体内有退出路径,否则就是死循环。但 Rust 社区和本课程都更推荐loop,原因有二:
- 意图更清晰:
loop明确表达"这个循环要一直跑,退出条件在循环体内部决定",而while true仍然暗示存在"条件检查"这一环节。 - 配合表达式返回值:
loop是 Rust 中唯一能保证"只在break处返回"的循环构造,因此它可以作为一个表达式产出值(详见下文),而while做不到这一点。
课程给出的经典应用场景是服务器:一个常驻进程用loop不断接受连接、处理请求,理论上永不退出,只有在收到关闭信号时才break。这类代码在并发专题(如 async.md 的聊天服务器、threads.md 的线程示例)中反复出现。
break与continue:控制循环节奏
要驾驭loop,必须掌握break和continue这对控制关键字。break-continue.md 给出了一个同时使用两者的完整示例:
fn main() { let mut i = 0; loop { i += 1; if i > 5 { break; } if i % 2 == 0 { continue; } dbg!(i); } }执行流程分析:
continue:立即开始下一次迭代。当i为偶数时,continue跳过后面的dbg!(i),直接进入下一轮i += 1。break:完全退出循环。当i超过 5 时,循环终止。- 最终输出:
i = 1、i = 3、i = 5(偶数被continue跳过,i = 6时因i > 5直接break)。
这段代码展示了二者的分工:continue处理"本次迭代该跳过的情况",break处理"整个循环该结束的情况"。
loop是唯一能返回值的循环
break-continue.md的<details>部分强调了一个语言层面的关键差异:loop是唯一可以返回非平凡(non-trivial)值的循环构造。
原因在于类型系统:loop保证只在break语句处返回,所以break可以携带一个表达式,作为整个loop表达式的求值结果;而while和for还可能在"条件失败"时自然退出,此时无值可返回,因此它们不能产生有意义的值。
fn main() { let mut n = 0; let result = loop { n += 1; if n >= 10 { break n * 2; // break 携带值,作为 loop 的结果 } }; println!("result = {result}"); // 输出 20 }注意break n * 2后面是分号,它终止循环的同时把n * 2作为loop表达式的值赋给result。这种写法常用于"反复尝试直到满足条件,并把最终值拿出来"的场景,例如重试逻辑、读取用户输入直到合法等。
嵌套循环与标签:精确跳出外层
当循环嵌套时,普通的break只能退出最内层循环。要一次跳出多层,需要用标签(label)。labels.md 展示了这一机制:
fn main() { let s = [[5, 6, 7], [8, 9, 10], [21, 15, 32]]; let mut elements_searched = 0; let target_value = 10; 'outer: for i in 0..=2 { for j in 0..=2 { elements_searched += 1; if s[i][j] == target_value { break 'outer; // 直接跳出外层循环 } } } dbg!(elements_searched); }要点:
- 标签写法是
'outer:,放在循环关键字之前。 break 'outer;从两层循环中一次性跳出,elements_searched最终值为 6([5,6,7,8,9,10]共 6 个元素被检查)。continue同样可以携带标签,用于"跳过外层循环的当前迭代"。
更进一步,课程指出标签break也能作用于任意代码块,而不只是循环:
fn main() { 'label: { break 'label; println!("This line gets skipped"); } println!("after block"); }break 'label;会直接离开被标记的{}块,块内println!永远不会执行,程序输出after block。这一特性在需要"从嵌套逻辑中提前整体退出"时非常实用。
真实场景一:嵌入式裸机中的loop {}
loop最常见的实战形态之一是"程序结束后的停驻点"。在课程的裸机(bare metal)章节中,main_minimal.rs 的 panic 处理函数这样写:
#[panic_handler] fn panic(info: &PanicInfo) -> ! { error!("{}", info); system_off::<Hvc>().unwrap(); loop {} // 永不返回,处理器在此停驻 }这里的loop {}是空的无限循环,没有任何break。它在无操作系统的no_std环境中扮演"系统停机"的角色:panic 后处理器没有可退出的地方,loop {}保证程序在错误后不会执行未定义行为,而是稳定地停住。这也是loop类型为!(永不返回类型)的直接体现,正因如此loop {}可以作为-> !函数的末尾表达式。
类似的模式在异步与并发代码中同样常见,例如事件循环、任务轮询的主干。
真实场景二:异步状态机中的loop + match
在 state-machine.md 中,课程展示了 Rust 如何把async fn编译成一个内部状态机,而其poll方法的主干正是loop:
impl Future for TwoD10 { type Output = u32; fn poll(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> { loop { match *self { TwoD10::Init { modifier } => { /* 发起第一次掷骰 */ } TwoD10::FirstRoll { modifier, fut } => { /* 等待第一次结果 */ } TwoD10::SecondRoll { modifier, first_roll, fut } => { return Poll::Ready(first_roll + second_roll + modifier); } } } } }loop包裹match是 Rust 中非常经典的习惯用法(loop-match 模式):每一轮迭代根据当前状态推进一"步",状态不满足条件就继续循环,直到达到终态后用return或break带值离开。这展示了loop不仅仅用于"服务器永远跑",也用于"反复推进直到完成"的算法式循环。
与for、while的分工:何时该用loop
回到 loops.md 的框架,三种循环的选用建议可以总结为:
| 循环 | 语义 | 适用场景 | 能否返回值 |
|---|---|---|---|
while | 条件为真时执行 | 有明确前置/运行时条件 | 否(非平凡值) |
loop | 无条件重复,靠break退出 | 常驻服务、重试、状态机、panic 停驻 | 是(仅loop) |
for | 遍历范围或集合 | 迭代元素,配合迭代器 | 否(非平凡值) |
其中for底层依赖迭代器(iterator)机制,课程在 for.md 中说明,并在后续专门的迭代器章节(iterators.md)深入展开。若你的循环有一个"范围"可遍历,首选for;若有一个条件可判断,首选while;若循环必须"无条件先跑起来、靠内部逻辑决定何时退出或携带什么值退出",那就该用loop。
配套练习:Collatz 序列
为了巩固控制流知识,课程在 exercise.md 安排了 Collatz 序列长度计算练习:对任意正整数 n,若为偶数则 n = n/2,若为奇数则 n = 3n + 1,直到 n 为 1,统计序列长度。其官方解答(见 exercise.rs 与 solution.md)使用while n > 1实现:
/// Determine the length of the collatz sequence beginning at `n`. fn collatz_length(mut n: i32) -> u32 { let mut len = 1; while n > 1 { n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 }; len += 1; } len } fn main() { println!("Length: {}", collatz_length(11)); // should be 15 }这个练习与本文主题直接相关:如果改用loop实现,写法是loop { ... if n <= 1 { break len; } ... }——这正是"用break携带返回值退出loop"的实战演练。解答中同时体现了mut参数、if作为表达式、隐式返回三个 Rust 特性。课程还提醒:该函数对n <= 0的输入可能陷入死循环或溢出(i32在 n 增长过大时溢出),这正是loop陷阱的绝佳教学素材——无限循环能力越强,越要确保存在可靠的退出条件。
小结
loop无条件无限循环,退出完全依赖break,等价于while true,但语义更清晰、能力更强。break可携带表达式,使loop成为唯一能返回非平凡值的循环;continue用于跳过本次迭代。- 嵌套循环可用
'label标签配合break 'label/continue 'label精确控制跳出层次,标签也可用于普通代码块。 - 实战形态包括:服务器常驻循环、
loop {}停机停驻(main_minimal.rs)、异步状态机的loop + match推进模式。 - 相关课程材料可在 loops.md、break-continue.md、labels.md、exercise.md 中继续深入,所有示例均可在课程仓库内直接阅读与运行。
【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考