while 循环
本教程共 78 篇 · 第 18 篇 · 更新于 2026-08-08 · 约 8 分钟阅读
本节目标:用
while写出”只要条件为真就一直做”的循环,理解它和 loop、for 的分工,避免条件永远为真导致的死循环,并能用 break/continue 控制流程。
loop 是”无条件一直转”,上一章讲过。但很多时候你要的不是”转个不停”,而是”某个条件成立期间转,不成立就停”。这种”当……时重复”的语义,用 while 最贴切。它把”退出条件”写在循环顶部,一眼就能看出循环什么时候会停,可读性比 loop + break 好很多。
18-1 基本写法
while 后面跟一个条件。每一轮开始前,编译器先判断条件:为真就执行循环体,为假就跳出,进入后面的代码。
fn main() {
let mut number = 3;
while number != 0 {
println!("{number}!");
number -= 1;
}
println!("发射!");
}
这段会倒数 3!、2!、1!,然后打印”发射!“。循环体每轮把 number 减 1,当 number 变成 0,number != 0 为假,循环结束。注意循环条件和循环体里”让条件变假的动作”是配套的——少了后者就转个没完。
Note和
if一样,while的条件必须是 bool。写while number {}(number 是整数)会编译报错,因为 Rust 不认”非零即真”。要写成while number != 0 {}或while number > 0 {}。这个规矩和 C 语言不同,初学者常在这一步踩坑。
18-2 用 while 做有界计数
借助一个会变的计数器,while 能实现”重复 N 次”:
fn main() {
let mut i = 0;
while i < 5 {
println!("第 {i} 次");
i += 1;
}
}
每轮 i 加 1,直到 i < 5 不成立为止。这能跑,但 Rust 更推荐用下一章的 for 做这种”固定次数”的循环——因为 while 写法里你要手动管 i += 1,漏写或写错就出问题。能数清楚次数的循环,for 才是更地道的选择。
18-3 经典陷阱:条件永远为真
while 最危险的坑,是条件永远为真,循环变成死循环:
let mut x = 0;
while x < 5 {
println!("卡住了"); // 忘了 x += 1,x 永远是 0,永远 < 5
}
这种 bug 不会编译报错(语法没毛病),但运行时程序卡死,CPU 被占满。所以写 while 时务必确认:循环体里的动作最终会让条件变假。如果条件依赖某个外部事件(比如”等到用户点取消”),要确保那条事件路径确实存在、一定能发生,否则程序会一直转。
Warning死循环不会在编译期被发现,危害比编译报错大。调试时,可以在循环体里临时打印条件变量(如
println!("x = {x}")),看它为啥一直不变。确认它最终会变假,再去掉打印。
18-4 while 和 break/continue
while 里照样能用 break 和 continue,行为和 loop 里一致。break 是”不管条件满不满足,立刻跳出整个循环”;continue 是”跳过本轮剩下的代码,直接回去判断条件、开始下一轮”。
let mut i = 0;
while i < 10 {
i += 1;
if i % 2 == 0 {
continue; // 偶数跳过本轮剩余代码
}
println!("奇数 {i}");
}
遇到偶数 continue 跳回条件判断,所以只打印奇数。continue 不会让条件变假,它只是跳过本轮。如果你需要”中途强制停”,用 break。
Warning
continue有个隐蔽陷阱:如果它出现在”让条件变假的代码”之前,就会跳过那行,导致条件永远为真。比如while x < 10 { if x % 2 == 0 { continue; } x += 1; }在x为偶数时会死循环——continue跳过了x += 1。写continue时务必确认条件最终能变假。
18-5 和 loop、for 怎么选
三种循环语义重叠,但有清晰分工,按语义选最贴合的:
- loop:纯无限,靠内部
break决定何时停。适合”先做、再判断是否继续”(如事件轮询、重试)。 - while:“当条件为真时重复”。条件写在顶部,一眼看出循环何时结束,可读性最好。适合”次数未知、只看条件”的场景。
- for:“对集合或区间里每个元素做一遍”。最适合遍历和固定次数,几乎不会写出死循环。
判断口诀:循环次数”跑之前就知道”吗?知道就用 for;不知道、只知道”满足某条件就停”就用 while;连条件都想写在里面就用 loop。选最贴合语义的,别人读你代码时不费劲。
18-6 while 做等待式轮询
while 很适合”轮询某个会变的状态”,比如等一个标志位被别的任务置真:
let mut ready = false;
while !ready {
// 做点准备工作,或休眠一小会儿
// 某个时刻 ready 被设为 true,循环自然结束
}
这类”等到某条件成立”的等待,while 表达得最自然。进阶并发章节会讲更优雅的等待机制(如通道、await),现阶段用 while 足够理解”条件驱动循环”这个模式。
18-7 while 与遍历的取舍
前面说过,固定次数的循环更推荐 for。但 while 在”条件驱动”的场景无可替代,比如读用户输入直到对方输入 quit、或轮询某个状态变化。这类”次数未知、只看条件”的任务,while 语义最贴切。
要特别强调:while 的条件在每一轮开头都会被重新求值。这意味着如果条件依赖的变量在循环体里变化了,下一轮的判断就是用新值算的。这既是它灵活的地方,也是坑的来源——你写的循环体必须真的去改变那个被判断的变量。很多死循环的根源,就是”条件里用的变量,循环体里根本没动它”,于是条件永远为真。养成习惯:写 while 时先想清楚”谁在让条件变假”,并在循环体里找到对应的那行代码。
一个常见误区是用 while 遍历数组时手动维护下标:
let a = [10, 20, 30];
let mut i = 0;
while i < a.len() {
println!("{}", a[i]);
i += 1;
}
这能跑,但有两个隐患:一是忘了 i += 1 就死循环;二是 a[i] 仍有越界风险(虽然 i < a.len() 守住了)。换成 for element in a 直接遍历元素,既无越界可能、也不用管下标,代码更短更安全。经验是:遍历集合用 for,单纯”条件循环”用 while。
18-8 while 读输入直到合法
while 常用于”读用户输入直到合法”这类交互循环。比如不断读文本,直到用户输入 “0” 才停(示意):
use std::io;
let mut line = String::new();
while line.trim() != "0" {
line.clear();
io::stdin().read_line(&mut line).unwrap();
println!("你输入了:{}", line.trim());
}
只要输入不是 “0”,循环就继续。循环次数完全未知,这种”以外部输入为终止条件”的场景,while 比 for 自然得多。
Warning交互式
while里如果忘了”清空上次输入”或”读新输入”,可能陷入用旧值反复判断的死循环。写这类循环时确认:每轮都真正拿到了新的、可能让条件变假的数据。另外read_line会连换行符一起读进来,所以要trim()掉再比较,否则 “0\n” 永远不等于 “0”。
18-9 while 与 for 的性能差异
while 和 for 在性能上几乎没差别——编译器会把它们都优化成高效的机器码,选哪个纯粹看语义是否贴切,不用担心”用错循环变慢”。经验上:遍历集合用 for,条件驱动用 while,靠内部 break 退出的用 loop。三者语义重叠,但各管一摊,选最贴合的那个,读代码的人一眼就懂循环意图。
Note有个细微点:
for遍历数组/切片时,底层用迭代器每次取下一个元素;while用手动下标则多一次”下标是否越界”的检查(运行时)。但for的迭代器版本这个检查被优化掉了,所以for反而常比手写while + 下标更优且更安全。能用for就优先for。
18-10 小结
while 条件 { } 在每轮开头判断条件,为真才执行;条件必须是 bool;小心漏掉让条件变假的代码导致死循环;循环体内可用 break(强制停)和 continue(跳本轮)。和 loop、for 的分工是:能数清次数用 for,条件驱动用 while,无条件死转用 loop。
还有一个细节值得记住:while 的条件表达式里不能做赋值(Rust 不允许 while x = get() 这种把赋值当条件,必须用 while let 或先赋值再判断),这是它有意为之,避免把 == 误写成 = 这种经典 C 语言 bug。所以条件里只能放”判断”,不能放”赋值”,这也让 while 的语义更干净。
下一章讲 for 和区间(range),它是遍历集合和”做固定次数”最地道、最安全的写法。