Rustlings 实战指南:用三个练习吃透 Rust 的 `if` 表达式与控制流
在 rustlings 中,03_if 练习组是学习 Rust 控制流的入口:它围绕 if 这一"最基本却异常灵活"的语法,设置了三个由浅入深的练习文件(if1.rs、if2.rs、if3.rs),配套说明见 exercises/03_if/README.md。读完并动手完成这三个练习后,你将掌握 Rust 中 if 的核心特性——if 是一个表达式而非单纯的语句、所有分支必须返回同一类型、以及为何 if 必须配有 else(或 panic)——这是 Rust 与 C/Java 等语言控制流最本质的区别之一。
1. 背景:练习组定位与运行方式
03_if 位于练习序列的第三位,排在变量(01_variables)与函数(02_functions)之后,官方建议配合 Rust 官方书籍的 Control Flow - if expressions 章节阅读(参见 exercises/03_if/README.md)。三个练习在练习清单文件 rustlings-macros/info.toml 中分别注册为 if1、if2、if3,每个文件都包含一个 #[cfg(test)] 测试模块,用单元测试断言来定义"正确行为",这是 rustlings 的典型教学模式:先让编译器和测试逼你写出符合 Rust 语义的代码。
典型的验证流程(前提是本机已安装 Rust 工具链):
# 运行某个练习文件中的所有单元测试
cargo test --test if1
# 或者使用 rustlings 官方提供的练习检查命令
rustlings check if1
练习完成后,可对照 solutions/03_if/ 目录下的三个官方答案文件核对思路。下面逐一拆解三个练习的编译器错误、修复方式与背后的语言规则。
2. if1:if 是表达式——把 if 的结果直接当作返回值
exercises/03_if/if1.rs 要求补全一个返回较大数的函数,并明确禁止引入额外函数调用或额外变量:
fn bigger(a: i32, b: i32) -> i32 {
// TODO: Complete this function to return the bigger number!
// If both numbers are equal, any of them can be returned.
// Do not use:
// - another function call
// - additional variables
}
关键考点:由于函数体只能写一个 if 表达式且不允许声明变量,if 表达式本身必须承担"返回值"的角色。官方答案 solutions/03_if/if1.rs 只有一行核心逻辑:
fn bigger(a: i32, b: i32) -> i32 {
if a > b { a } else { b }
}
这里有三个容易忽略的细节:
- 分支体是不带分号的表达式:
{ a }与{ b }中的a、b是分支表达式的值,而不是赋值语句。若写成{ a; }分号会吞掉表达式的值,导致该分支类型为(),引发类型不匹配错误。 - 每个分支的类型必须一致:
a与b都是i32,与函数声明的返回类型吻合,因此if表达式的整体类型就是i32,可以直接作为函数尾表达式返回。 - 相等分支的处理:当
a == b时,a > b为假,走else分支返回b,即两数之一,满足题目注释中"相等时任返回哪个都行"的要求。
文件底部的三个测试(exercises/03_if/if1.rs#L18-L31)分别覆盖了"前者更大"(bigger(10, 8) 应为 10)、"后者更大"(bigger(32, 42) 应为 42)、"两数相等"(bigger(42, 42) 应为 42)三种情况,与上述分析一一对应。
3. if2:所有分支必须返回同一类型——修复 picky_eater
exercises/03_if/if2.rs 给出了一段无法通过编译的代码,要求按测试行为修复:
// TODO: Fix the compiler error on this function.
fn picky_eater(food: &str) -> &str {
if food == "strawberry" {
"Yummy!"
} else {
1
}
}
错误点一目了然:if 分支返回字符串字面量 "Yummy!"(类型 &str),而 else 分支返回整数 1(类型 i32)。这正是 if 作为表达式必须满足的分支类型一致性规则——if 表达式的类型由所有分支共同决定,编译器不会做任何隐式类型转换。
对照测试可以推断出期望行为(exercises/03_if/if2.rs#L20-L36):"strawberry" 返回 "Yummy!";"potato" 返回 "I guess I can eat that.";其余任何输入("broccoli"、"gummy bears" 等)一律返回 "No thanks!"。修复方案就是补全 else if 分支并统一类型为 &str,官方答案 solutions/03_if/if2.rs 如下:
fn picky_eater(food: &str) -> &str {
if food == "strawberry" {
"Yummy!"
} else if food == "potato" {
"I guess I can eat that."
} else {
"No thanks!"
}
}
这个练习同时演示了 else if 链的写法:Rust 没有独立的 else-if 关键字,而是通过嵌套 if 实现链式判断,且最后一个 else 兜底分支保证了表达式"总有值可返回"。
4. if3:多分支链中的类型一致性——animal_habitat
exercises/03_if/if3.rs 把类型一致性问题放大到更长的分支链。题目代码用一组 identifier 把"动物"映射为中间值,再由第二个 if 链映射为栖息地字符串:
let identifier = if animal == "crab" {
1
} else if animal == "gopher" {
2.0
} else if animal == "snake" {
3
} else {
"Unknown"
};
这里混入了三种类型:整数 1、3(默认推断为 i32)、浮点数 2.0(默认 f64)、字符串 "Unknown"(&str)。第一个 if 链各分支类型各不相同,必然编译失败——与 if2 同源的规则,只是分支更多。
官方答案 solutions/03_if/if3.rs 的修复策略很巧妙:把所有中间分支统一为整数,并给兜底分支也分配一个未使用的整数标识(如 4),使第一个 if 链整体成为 i32 表达式:
let identifier = if animal == "crab" {
1
} else if animal == "gopher" {
// Integer, so that every branch has the same type.
2
} else if animal == "snake" {
3
} else {
// Any unused identifier.
4
};
第二个 if 链保持不变,它本身类型一致(四个分支都是 &str),通过 identifier == 1/2/3 分别映射到 "Beach"、"Burrow"、"Desert",其余值落入 "Unknown" 兜底,因此传任何未知动物(如测试中的 "dinosaur")都会得到 "Unknown"。
值得注意的是答案中的这条注释:"Instead of such an identifier, you would use an enum in Rust." 从源码注释看,出题者的意图是引导学习者意识到:用整数编码代替类型区分只是本练习的权宜之计,地道的 Rust 写法应使用枚举——这与后续练习组 08_enums 形成呼应。这也体现了一个通用原则:当分支需要携带不同语义的值时,优先选择表达力更强的类型系统工具,而不是脆弱的整数魔数。
5. 小结:从三个练习提炼的 Rust if 规则
综合 exercises/03_if/ 三个练习及其 solutions/03_if/ 参考答案,可以把 Rust 中 if 的关键规则归纳为四点:
if是表达式:每个分支体是一个表达式,其值参与整体求值,可直接作为返回值或let绑定的值(if1 中if a > b { a } else { b }直接充当函数返回值)。- 分支类型必须一致:所有
if/else if/else分支表达式必须收敛到同一类型,编译器不做隐式转换(if2 的&strvsi32、if3 的i32vsf64vs&str都是反例)。 else不可省略:因为if必须总有值,没有else时编译器会报错(除非条件分支以panic!等 diverging 表达式结束)。else if链用于多路判断:语法上是else后嵌套新的if,最后一个else承担兜底职责。
完成本组练习后,你可以继续推进到 04_primitive_types,或对照 solutions/ 目录逐一复盘三个答案文件,确认自己对表达式求值与类型推断的理解已经稳固。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00