首页
/ Rustlings 实战指南:用三个练习吃透 Rust 的 `if` 表达式与控制流

Rustlings 实战指南:用三个练习吃透 Rust 的 `if` 表达式与控制流

2026-09-03 17:13:59作者:胡易黎Nicole

rustlings 中,03_if 练习组是学习 Rust 控制流的入口:它围绕 if 这一"最基本却异常灵活"的语法,设置了三个由浅入深的练习文件(if1.rsif2.rsif3.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 中分别注册为 if1if2if3,每个文件都包含一个 #[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 } 中的 ab 是分支表达式的值,而不是赋值语句。若写成 { a; } 分号会吞掉表达式的值,导致该分支类型为 (),引发类型不匹配错误。
  • 每个分支的类型必须一致ab 都是 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"
};

这里混入了三种类型:整数 13(默认推断为 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 的关键规则归纳为四点:

  1. if 是表达式:每个分支体是一个表达式,其值参与整体求值,可直接作为返回值或 let 绑定的值(if1 中 if a > b { a } else { b } 直接充当函数返回值)。
  2. 分支类型必须一致:所有 if/else if/else 分支表达式必须收敛到同一类型,编译器不做隐式转换(if2 的 &str vs i32、if3 的 i32 vs f64 vs &str 都是反例)。
  3. else 不可省略:因为 if 必须总有值,没有 else 时编译器会报错(除非条件分支以 panic! 等 diverging 表达式结束)。
  4. else if 链用于多路判断:语法上是 else 后嵌套新的 if,最后一个 else 承担兜底职责。

完成本组练习后,你可以继续推进到 04_primitive_types,或对照 solutions/ 目录逐一复盘三个答案文件,确认自己对表达式求值与类型推断的理解已经稳固。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384