表达式与控制流:让分支、循环和返回值清楚可审查
课程概览 · 第 4 章
Rust 的 if、match、块和 loop 都可以产生值。控制流设计的目的不是消灭临时变量,而是让分支如何汇合、循环何时退出、失败如何返回在代码中清楚可见。
学习目标
前置知识:绑定、基本类型、Option / Result 与切片。学完你能:
- 用分支和块产生明确类型的值;
- 根据尾表达式与分号定位意外的
(); - 按迭代来源与退出条件选择循环;
- 用显式分支和命名错误完成输入校验。
本章只依赖标准库,采用 Edition 2024。独立示例各自放入 binary 项目的 src/main.rs,用 cargo +stable run 执行;故意失败例单独编译,不与正常例拼接。
if 产生一个值
if 整体只有一个类型,分支需要汇合为共同类型;常见情况是两个分支返回相同类型,也允许强制转换,如不返回的 ! 路径转换到另一分支的类型。不能仅凭表面写法不同就断定类型不匹配。需要表示缺值时用 Option<T>,不让魔法值同时承担“正常值”和“没有结果”。
独立示例 04-1(标准库;文件:src/main.rs)。
1 | |
尾表达式与分号
块尾的无分号表达式提供块的值。若最后一条是带分号的表达式语句且没有尾表达式,块的值是 ()。分号不是把原表达式内部的类型改掉,而是丢弃结果,影响所在块的返回值。let 是声明语句;赋值本身是值为 () 的表达式,不能把所有赋值统称为“不是表达式”。
故意失败:编译错误 04-2(标准库;文件:src/main.rs;尾分号使函数体为 (),与声明的 u64 不匹配,类别 E0308)。
1 | |
去掉尾分号即可让 if 的结果作为返回值。也可以明确使用 return,但末尾没有特殊早退需求时通常保留尾表达式。
独立示例 04-3(标准库;文件:src/main.rs)。
1 | |
let x = y = 5; 不会让 x 成为整数 5,赋值表达式给出的是 ()。第 1 章的 () 只有一个值,这与返回 Option::None 是不同语义。
match:结构分支与穷尽性
match 的分支同样汇合为共同类型,而且必须穷尽被匹配类型的所有情况。数值范围不能只覆盖示例输入就当作覆盖整个类型。业务状态会在第 8、9 章用枚举建模;本章先观察 u8 的覆盖。
独立示例 04-4(标准库;文件:src/main.rs)。
1 | |
删掉 _ 就遗漏 4..=255,编译器会拒绝非穷尽匹配。穷尽性检查证明覆盖类型的可能情况,不证明提示语符合业务需求。模式如何移动或借用数据留给第 9 章。
loop、while、for 与 break
| 需求 | 写法 | 值从哪里来 |
|---|---|---|
| 遍历已有序列 | for item in &items | 共享借用元素 |
| 修改已有序列 | for item in &mut items | 可变借用元素 |
| 按值遍历 | for item in items | IntoIterator 决定,非 Copy 集合通常移动 |
| 条件为真时重复 | while condition | 不预设集合所有权 |
| 重复匹配成功值 | while let Some(x) = expr | 取决于 expr 返回拥有值还是引用 |
| 循环体内部决定结束 | loop | break value 可以提供循环结果 |
while let 本身不保证消费集合:pop_front() 移出队列元素,iter.next() 则可以返回引用。for 是否消耗原容器也取决于迭代来源,例如 Copy 数组按值迭代是复制数组。
独立示例 04-5(标准库;文件:src/main.rs)。
1 | |
break 结束目标循环,continue 跳到下次迭代。普通 break 给 loop 提供 (),break value 可以产生其他结果;while、for 不支持这种带值退出。循环累加仍有整数溢出边界,本例只对给定小输入断言,不把控制流正确当作任意规模计算都正确。
守卫式校验:parse_limit
解析失败与业务拒绝是两种错误。String 错误消息适合展示,但不提供稳定的结构分支;本例以 LimitError 的变体区分“不为数值”和“零”。完整 Display / Error 实现在第 13 章设计,不在这里抢先引入。
独立示例 04-6:输入校验主例(标准库;文件:src/main.rs)。
1 | |
解析成功分支提供 usize,失败分支用 return 退出,不需要提供继续执行的值,这也体现了分支汇合允许发散路径。随后用 if 拒绝 0,末尾只剩成功路径。这里的零限制是本例业务规则;超出 usize 范围的文本也归入 NotANumber,因为本例没有进一步细分解析错误。下一章在显式分支的基础上引入 ?。
边界提醒
表达式有类型,声明语句与表达式语句承担组织执行的角色,不能统一声称“语句的类型都是 ()”。需要嵌套时先判断是否真的存在层级关系,互斥状态通常更适合 enum + match。已证明的内部索引可以使用,遍历元素时则不必绕道索引;这些选择解决的是可读性与合同,不是风格上的绝对禁令。
自测
if分支表面类型不同就一定编译失败吗?- 函数声明返回
u64,错误却说找到(),应先检查什么? for item in items与while let是否必定移动整个原容器?parse_limit("abc")与parse_limit("0")如何区分?break value可以在哪类循环中提供结果?
参考答案
- 不一定,分支须汇合为共同类型,允许相应强制转换,发散的
!路径也是例子。 - 检查函数体的尾表达式是否多了分号,或根本没有返回值。
- 不一定。所有权取决于迭代来源和被匹配表达式;非
Copy集合按值迭代通常移动,while let可以取得引用。 - 显式解析分支返回
NotANumber,零值守卫返回Zero,调用方无需解析错误字符串。 loop支持带值break;本章的while、for不支持。






