Rust 控制流基础:Comprehensive Rust 中的if表达式详解
【免费下载链接】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
if是 Rust 中最基础的控制流构造,但它与 C、Java 等语言最大的不同在于:Rust 的if是一个表达式(expression)而非语句(statement),它可以产生值、可以出现在let绑定右侧,还能与类型系统进行强制的分支类型统一检查。本文以 Google Android 团队维护的 Comprehensive Rust 课程中ifexpressions 一节为核心,结合课程源码与配套练习,系统讲解if表达式的语法、分支类型一致性、尾表达式求值规则,以及它在实际代码(如 Collatz 序列练习)中的惯用写法。
一、课程定位:if在 Comprehensive Rust 中的位置
if表达式属于课程 "Day 1: Morning" 的Control Flow Basics章节,位于 控制流基础总览 之下。根据课程目录 SUMMARY.md 的编排顺序,本节的前置知识是 Blocks and Scopes(块与作用域),后续则衔接 match 表达式、循环(while/loop/for) 与 break 和 continue。
该节标注的授课时长为4 分钟,属于快速推进的入门内容。课程意图非常明确:先让学员掌握if的“类语句”用法,再揭示其“表达式”本质,为后面理解match、函数尾表达式返回、以及一切“块即表达式”的 Rust 核心哲学铺路。
Control Flow Basics ├── Blocks and Scopes ← 块与作用域(表达式基础) ├── if Expressions ← 本文主题 ├── match Expressions ├── Loops(while / loop / for) ├── break 和 continue(含标签) ├── Functions └── Macros二、作为“语句”使用的if:与其他语言的相似之处
Rust 的if在常规场景下与其他语言的if语句用法完全一致:根据条件执行不同分支。
fn main() { let x = 10; if x == 0 { println!("zero!"); } else if x < 100 { println!("biggish"); } else { println!("huge"); } }运行结果是打印biggish。这里有几个值得注意的语法细节:
- 条件不需要括号:
if x == 0而非if (x == 0),圆括号是可选的(作为表达式的一部分时括号也经常省略); - 分支必须用花括号包裹:即使分支体只有一行,
{}也不可省略。这与其他语言(如 Python 的缩进、Go 的强制花括号)风格不同,避免了悬挂else(dangling else)问题; else if链:Rust 没有独立的elseif关键字,else if是多个if表达式的嵌套组合,可任意串联。
课程原文强调“You useifexpressions exactly likeifstatements in other languages”,即这一层用法对任何有编程经验的开发者都是零门槛的。
三、核心特性:if是表达式,可以产生值
if真正区别于“语句式”语言的地方在于:每个分支块的最后一个表达式会成为整个if表达式的值。
fn main() { let x = 10; let size = if x < 20 { "small" } else { "large" }; println!("number size: {}", size); }输出:
number size: small这里if x < 20 { "small" } else { "large" }整体被求值为"small",并绑定给变量size。变量size的类型是&str,由两个分支共同决定。
这种写法的价值在于:
- 消除可变绑定:不需要先
let mut size = "large";再在分支里赋值,变量声明即完成初始化,天然符合 Rust 的所有权与不可变优先风格; - 表达式嵌套:
if可以出现在任何期望值的位置——let绑定、函数参数、return语句、甚至另一个if的分支里; - 配合尾表达式:函数体最后一个
if表达式可以直接作为返回值(见下文第五节)。
四、分支类型必须一致:编译器的强制约束
因为if是表达式且“必须具有某个特定类型”(must have a particular type),两个分支块必须具有相同的类型。这是 Rust 类型系统给出的强保证。
课程在隐藏备注(<details>)中提供了一个很好的课堂实验:在第二个示例中给"small"加上分号,即:
let size = if x < 20 { "small"; } else { "large" };这样"small";所在分支的块尾表达式变成了单元类型()(因为分号会吞掉表达式值),而else分支仍然是&str,两分支类型不一致,编译器会直接报错。这也是理解 块与作用域 中“如果最后一个表达式以;结尾,则块的值为()”这一规则的关键应用。
同理,if表达式作为表达式使用时必须遵守普通表达式语法:当它出现在let语句中时,整个let语句同样要以;结束。课程建议删除println!前的;观察编译错误,这正是让学员体会“let绑定语句必须以分号收尾”这一句法规则的实战演练。
// 正确写法:let 语句以分号结束 let size = if x < 20 { "small" } else { "large" }; // ← 这行末尾的分号不能少 // 错误写法:缺少分号会导致语法错误 // let size = if x < 20 { "small" } else { "large" } println!("number size: {}", size);分支一致性的意义
- 类型安全:
if的结果可以被安全地赋给任意类型标注变量,编译器在编译期就能发现分支类型分叉的 bug; - 可穷举性:配合
match的穷尽性检查(match 表达式),Rust 保证每个表达式路径都有确定的值,没有“隐式返回 null”的暗坑。
五、if表达式的惯用场景:函数尾返回与循环体内赋值
5.1 函数体的尾表达式返回
Rust 函数体中“最后一个表达式成为返回值”。因此if表达式可以直接充当函数返回值,这是课程 函数 一节展示的写法:
fn gcd(a: u32, b: u32) -> u32 { if b > 0 { gcd(b, a % b) } else { a } }这个递归求最大公约数的函数,用单个if表达式直接返回结果,完全不需要return关键字——if表达式本身的值就是函数的返回值。
5.2 循环体内直接赋值
Collatz 序列练习 的官方解答 exercise.rs 中,if表达式被用在while循环体内直接给变量赋值:
/// 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 }课程对应的 解答说明 明确指出了这一写法的优势:
ifexpressions:Rust'sifis an expression, meaning it produces a value. We assign the result of theif/elseblock directly ton. This is more concise than writingn = ...inside each branch.
即:n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 };比在分支内分别写n = n / 2;和n = 3 * n + 1;更简洁。配套测试也验证了结果:
#[test] fn test_collatz_length() { assert_eq!(collatz_length(11), 15); }这种“表达式即值”的风格,让数据流的每一步变换都清晰可见,是 Rust 代码的惯用写法。
六、与match的分工:什么时候用if,什么时候用match
课程在 match 表达式 一节中专门讨论了if与match的取舍:
- 两分支布尔判断:
if {} else {}与match一个bool的写法几乎等价,此时用if更自然; - 多分支匹配:当需要检查一个值的多个可能取值时,
match比if {} else if {} else if {}链条更简洁、更安全(match强制穷尽所有可能性,且分支间不会像 C 的switch那样贯穿 fall-through)。
// match 版本:多分支时更清晰 let val = match flag { true => 1, false => 0, };因此,本节的if知识是理解后续match的阶梯:两者同为“表达式”、同有“分支求值”的语义,只是match更擅长多分支与模式匹配。
七、课堂演示建议与易错点总结
课程为讲师提供了三条课堂演示建议,对自学者同样适用:
- 观察分号对分支类型的影响:在
if x < 20 { "small" } else { "large" }的"small"后加分号,观察类型不匹配的编译错误——直观体会“块尾表达式决定块类型”; - 观察语句结尾分号的影响:删除
let size = ...;末尾的分号,体会let语句的句法要求; - 对比等价写法:把表达式式
if改写成传统的先声明后赋值形式,感受表达式的简洁性。
汇总本节的易错点:
| 场景 | 正确写法 | 常见错误 |
|---|---|---|
| 分支类型 | 两分支类型必须一致 | 一个分支尾表达式加分号变为(),类型分叉报错 |
let绑定 | let x = if cond { a } else { b };以分号收尾 | 忘记末尾分号导致语法错误 |
| 条件括号 | if x > 0即可 | 误写成 C 风格if (x > 0)(不报错但非 Rust 惯例) |
| 分支花括号 | if cond { ... }不可省略花括号 | 省略花括号直接报错 |
八、扩展阅读
在 Comprehensive Rust 仓库中,围绕if表达式可以继续阅读:
- Blocks and Scopes:块是表达式的容器,最后一个表达式的值决定整个块的值;
- match 表达式:多分支表达式求值,与
if互补; - 循环 与 break/continue:
while条件本质上也是布尔表达式; - 函数:尾表达式返回与
if表达式结合的最佳实践; - Collatz 练习 与 解答:在完整练习中体会
if表达式的实战价值。
掌握if是理解 Rust “表达式导向”(expression-oriented)编程风格的第一步——从这里的let size = if ...到函数尾返回、再到match与闭包,Rust 的整个语言设计都围绕“一切皆表达式”展开,这正是它区别于传统命令式语言的核心哲学。
【免费下载链接】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),仅供参考