Rust 控制流程:把程序“跑起来”的规则讲清楚(if / loop / while / for)

写程序本质上就是两件事:

  1. 根据条件决定走哪条路(分支)
  2. 让一段代码重复执行(循环)

Rust 中最常见的控制流程构造就是 if 表达式三种循环:loop / while / for。这篇文章会按“从能跑到写得优雅”的顺序,把它们讲成一条连续的学习路线。


0. 先记住两条 Rust 风格的规则

规则 1:if 的条件必须是 bool

Rust 不会像某些语言那样把 0/1、空字符串、空数组自动当作真假。 条件表达式的结果必须是 truefalse

规则 2:很多构造都是“表达式”,可以产生值

Rust 里 if 是表达式,所以可以写 let x = if ... { ... } else { ... }; 这会让代码更紧凑,但也带来一个要求:两个分支产生的值必须是同一类型


1. 从最简单的分支开始:if / else

新建项目(可选):

cargo new branches
cd branches

写入 src/main.rs

fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}

运行:

Terminal window
cargo run

发生了什么?

  • if 后面跟一个条件:这里是 number < 5
  • 条件为 true 就执行 { ... } 里的代码块
  • 否则执行 else { ... }

没有 else 会怎样?

如果条件为 false,Rust 会跳过 if 块,继续执行后面的代码。


2. 最常见的新手错误:把整数当条件

下面这段不能编译

fn main() {
let number = 3;
if number {
println!("number was three");
}
}

原因很简单:number 是整数,不是布尔值。 如果你想表达“number 不是 0”,就把条件写清楚:

fn main() {
let number = 3;
if number != 0 {
println!("number was something other than zero");
}
}

经验:Rust 的“显式”会让代码更可靠。你看到条件,就能明确知道程序为什么进入这个分支。


3. 多条件分支:else if(顺序很重要)

当条件不止一个时可以这样写:

fn main() {
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
}

关键点:只执行“第一个为真”的分支

  • Rust 会从上往下检查
  • 一旦命中一个 true,就不会再检查后面的条件

因此 6 同时能被 32 整除,但只会打印 divisible by 3,因为那条分支更靠前。

else if 链条越来越长时,可读性会变差。后续遇到“多分支 + 不同情况”的场景,通常会使用更强的分支构造(例如 match),不过这篇文章先把 if 和循环讲扎实。


4. 把 if 当成值来用:let 右侧的 if

因为 if 是表达式,你可以这样写:

fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");
}

这里发生了什么?

  • 如果 condition 为真,if 表达式的结果是 5
  • 否则结果是 6
  • 这个结果被赋值给 number

必须同一类型:否则编译失败

下面这段不能编译

fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is: {number}");
}

原因:if 分支返回 i32else 分支返回 &str,类型不一致。 Rust 需要在编译期确定 number 的唯一类型。

经验:当你想用 if 表达式生成一个值时,提前想好“两个分支最终要得到同类东西”。


进入循环:让代码重复执行

Rust 有三种循环:loop / while / for。它们解决同一类问题,但适合的场景不同。


5. loop:最原始的“无限循环”,必须手动停

loop 的语义非常直接:一直重复,直到你明确让它停止。

fn main() {
loop {
println!("again!");
}
}

运行后会不停输出,终端一般用 Ctrl + C 中断。

5.1 break:退出循环

你可以用 break 在某个条件满足时停止循环:

fn main() {
let mut n = 0;
loop {
n += 1;
if n == 3 {
break;
}
println!("n = {n}");
}
println!("done");
}

5.2 continue:跳过本轮剩余代码,进入下一轮

fn main() {
let mut n = 0;
loop {
n += 1;
if n % 2 == 0 {
continue; // 偶数跳过
}
println!("odd n = {n}");
if n == 5 {
break;
}
}
}

经验:loop 很适合“先做一件事,失败就重试”的结构,比如读输入、解析、检查、再决定是否退出。


6. loop 还能“返回值”:break 值

这是 Rust 很有特色的地方:break 可以携带一个值,作为整个 loop 表达式的结果。

fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // 把值带出去
}
};
println!("The result is {result}");
}

这段代码的阅读方式是:

  • 循环一直跑
  • counter == 10 时停止
  • 停止时把 counter * 2 作为 result 的值

break vs return

  • break:只退出当前循环
  • return:直接退出当前函数(不管你在多少层循环里)

7. 嵌套循环时,break 到底退出哪一层?用“循环标签”讲清楚

默认情况下:

  • break / continue 只作用于最内层循环

如果你需要跳出外层循环,可以给外层循环加标签(标签以单引号开头):

fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break; // 只退出内层 loop
}
if count == 2 {
break 'counting_up; // 退出外层 loop
}
remaining -= 1;
}
count += 1;
}
println!("End count = {count}");
}

经验:标签不是常用武器,但在“多层循环 + 需要精准跳出”的场景非常干净。


8. while:当“条件为真就继续”,条件为假就停止

很多循环都是这种模式: “只要还满足条件,就继续跑;否则停止”。

while 写倒计时:

fn main() {
let mut number = 3;
while number != 0 {
println!("{number}!");
number -= 1;
}
println!("LIFTOFF!!!");
}

为什么不用 loop + if + break

当然能写,但会更啰嗦、嵌套更深。 while 就是把最常见的“条件循环”结构变成了语言级构造,读起来更直接。


9. for:遍历集合的首选(更安全、更简洁)

9.1 先看一个“容易出错”的 while 遍历数组

fn main() {
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", a[index]);
index += 1;
}
}

这段代码的问题是:

  • 5 是手写的,如果数组长度改了,你可能忘记同步修改
  • 条件写错会导致越界 panic 或漏掉元素
  • 每次循环都要检查索引边界,结构更繁琐

9.2 用 for 遍历:不写索引,也不怕长度变化

fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
}

优点:

  • 更短
  • 更不容易出错
  • 通常更高效(不需要每轮都手动做索引比较逻辑)

经验:在 Rust 中,只要是“遍历集合”,优先 for


10. for 也能做“跑固定次数”:用范围 Range

倒计时用 for 可以写得更顺:

fn main() {
for number in (1..4).rev() {
println!("{number}!");
}
println!("LIFTOFF!!!");
}

解释一下:

  • 1..4 生成 1、2、3(右边不包含 4)
  • .rev() 把顺序反转成 3、2、1
  • for 逐个取出并执行循环体

11. 选择哪种循环?一张决策表解决 80% 纠结

  • 需要无限重试,直到某个时刻成功或手动退出loop
    • 常见:读输入 → 解析 → 不合法就 continue → 合法且满足条件就 break
  • 条件控制“满足就继续、不满足就停”while
    • 常见:倒计时、状态轮询(简单版)
  • 遍历集合 / 运行固定次数for
    • 常见:数组、向量、范围循环,几乎都是首选

12. 小练习(把本章知识变成手感)

  1. 写一个程序:
    • let n = 10;
    • if/else if/else 判断 n 能否被 2/3/5 整除,输出第一条命中的规则。
  2. 写一个 loop
    • 从 1 加到 100
    • 遇到 17 的倍数跳过(continue
    • 当总和超过 500 时停止,并用 break sum 把 sum 作为结果返回。
  3. 写一个 for
    • 遍历数组 [3, 1, 4, 1, 5]
    • 打印每个元素的平方。