Rust: Rust 流程控制:if-else、match 与 if let 完全指南
最后更新:2026-08-26
Rust 的 match 不是 switch——它更强大,因为编译器会检查你是否覆盖了所有情况。
控制流是程序的骨架。Rust 除了有传统的 if/else,还有两大王牌:match 模式匹配和 if let 语法糖。
1. 你将学到
- 用
if/else if/else做条件分支 - 用
match做模式匹配 - match 的穷尽性检查和通配符
_ - 用
if let简化单模式匹配 - match 守卫(guard)添加额外条件
2. 猜拳游戏裁判的故事
(1) 痛苦:用 if-else 写猜拳判定
Emma 是一名游戏开发者,她想在游戏中加入石头剪刀布功能:
TEXT
📖 仅展示
玩家出:石头(1)、剪刀(2)、布(3)
电脑出:随机一个
需要判定:赢、输、还是平局
用 if/else 判定的代码:
TEXT
📖 仅展示
if 玩家 == 1 && 电脑 == 1 → 平局
else if 玩家 == 1 && 电脑 == 2 → 赢
else if 玩家 == 1 && 电脑 == 3 → 输
... 还需要写 6 个分支!
写了一大堆
if/else,容易漏掉某些组合,而且难以阅读。
(2) Rust match 的优雅解法
RUST
fn main() {
let player = 1; // 石头
let computer = 2; // 剪刀
let result = match (player, computer) {
(1, 1) | (2, 2) | (3, 3) => "平局",
(1, 2) | (2, 3) | (3, 1) => "你赢了!",
(1, 3) | (2, 1) | (3, 2) => "你输了!",
_ => "无效出拳",
};
println!("结果: {}", result);
}
match在这就像一个裁判:它把玩家和电脑的出拳组合一一对应,没有遗漏,没有歧义。编译器会检查你是否写了所有 9 种组合。
3. if/else 条件分支
(1) 基本用法
RUST
fn main() {
let number = 7;
if number < 5 {
println!("数字小于 5");
} else if number == 5 {
println!("数字等于 5");
} else {
println!("数字大于 5");
}
}
(2) if 是表达式——可以返回值
RUST
let condition = true;
let value = if condition { 5 } else { 6 };
// 注意:两个分支必须是同一类型
println!("value = {}", value); // value = 5
重要:
if的两个分支必须是同一种类型。if true { 5 } else { "six" }会编译报错。
4. match 模式匹配
(1) 基本语法
graph TB
A[match 表达式] --> B[每一条分支 = 一个模式]
B --> C[模式匹配成功 → 执行该分支]
B --> D[模式不匹配 → 尝试下一个分支]
A --> E[最后必须有 _ 通配符兜底]
RUST
let number = 3;
match number {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("other"), // 通配符——匹配所有剩余情况
}
(2) match 关键规则
| 规则 | 说明 |
|---|---|
| 穷尽性 | 必须覆盖所有可能的值,缺少分支会编译报错 |
通配符 _ |
匹配所有剩余情况,放在最后 |
多模式 | |
一个分支匹配多个模式:1 | 2 => |
| 范围匹配 | 1..=5 匹配 1 到 5 的闭区间 |
| 守卫 guard | 额外条件:x if x > 5 => |
(3) if 与 match 对比
| 特性 | if/else |
match |
|---|---|---|
| 匹配方式 | 布尔条件 | 模式匹配 |
| 穷尽性检查 | 无(else 可省略) | 强制(必须覆盖所有值) |
| 解构能力 | 无 | 支持解构枚举、元组、结构体 |
| 多值匹配 | 需要 ` | |
| 适用场景 | 范围判断、布尔逻辑 | 枚举处理、值分类、解构 |
5. if let 语法糖
当只关心某个特定模式、不关心其他值时,if let 比 match 更简洁:
RUST
let optional = Some(5);
// match 写法(啰嗦)
match optional {
Some(value) => println!("值为: {}", value),
_ => (), // 必须写这个空分支
}
// if let 写法(简洁)
if let Some(value) = optional {
println!("值为: {}", value);
// 不需要写 _ => ()
}
(4) 控制流关键字速查
| 关键字/语法 | 用途 | 示例 |
|---|---|---|
if / else if / else |
条件分支 | if x > 0 { ... } else { ... } |
match |
模式匹配 | match x { 1 => ..., _ => ... } |
if let |
单模式匹配语法糖 | if let Some(v) = x { ... } |
while |
条件循环 | while x > 0 { ... } |
while let |
模式匹配循环 | while let Some(v) = iter.next() { ... } |
loop |
无限循环 | loop { ... break; } |
for..in |
迭代器遍历 | for item in collection { ... } |
break |
跳出循环 | break; 或 break value; |
continue |
跳过本次迭代 | continue; |
6. 完整示例
▶ 示例 1:if/else 成绩分级(难度 ⭐)
RUST
// ============================================
// 用 if 表达式给成绩分级并返回字符串
// ============================================
fn main() {
let score = 88;
let grade = if score >= 90 {
"优秀 (A)"
} else if score >= 80 {
"良好 (B)"
} else if score >= 70 {
"中等 (C)"
} else if score >= 60 {
"及格 (D)"
} else {
"不及格 (F)"
};
println!("分数: {}, 等级: {}", score, grade);
}
输出:
TEXT
📖 仅展示
分数: 88, 等级: 良好 (B)
if表达式的每个分支都必须返回相同类型。这里所有分支都返回&str,所以编译器高兴。
▶ 示例 2:match 处理 HTTP 状态码(难度 ⭐⭐)
RUST
// ============================================
// 用 match 优雅处理 HTTP 状态码
// ============================================
fn handle_status_code(code: u16) -> &'static str {
match code {
200 => "OK: 请求成功",
201 => "Created: 资源已创建",
204 => "No Content: 无返回内容",
301 | 302 => "Redirect: 资源已移动",
400 => "Bad Request: 请求格式错误",
401 => "Unauthorized: 未授权",
403 => "Forbidden: 禁止访问",
404 => "Not Found: 资源不存在",
500 => "Internal Server Error: 服务器内部错误",
502 | 503 => "Server Error: 服务器暂时不可用",
_ => "Unknown: 未知状态码",
}
}
fn main() {
let codes = [200, 404, 418, 500];
for &code in codes.iter() {
println!("状态码 {}: {}", code, handle_status_code(code));
}
}
输出:
TEXT
📖 仅展示
状态码 200: OK: 请求成功
状态码 404: Not Found: 资源不存在
状态码 418: Unknown: 未知状态码
状态码 500: Internal Server Error: 服务器内部错误
|操作符可以让一个分支匹配多个模式,避免重复代码。_通配符确保所有状态码都被覆盖。
▶ 示例 3:match 守卫和 if let(难度 ⭐⭐)
RUST
// ============================================
// match 守卫 + if let 简化模式匹配
// ============================================
fn main() {
let number = Some(42);
// match 守卫:在模式基础上加额外条件
match number {
Some(x) if x > 100 => println!("超大数字: {}", x),
Some(x) if x > 50 => println!("大数字: {}", x),
Some(x) => println!("小数字: {}", x),
None => println!("没有数字"),
}
// if let 语法糖——只关心 Some 的情况
let value = Some("Rust");
if let Some(lang) = value {
println!("语言是: {}", lang);
}
// 带 else 的 if let
let empty: Option<i32> = None;
if let Some(x) = empty {
println!("有值: {}", x);
} else {
println!("没有值");
}
}
输出:
TEXT
📖 仅展示
小数字: 42
语言是: Rust
没有值
match 守卫用
if关键字在模式后添加额外条件——只有模式匹配且条件为真时才会进入该分支。if let适合"只关心一种模式"的场景。
▶ 示例 4:match 解构枚举与元组(难度 ⭐⭐⭐)
RUST
// ============================================
// match 解构:枚举 + 元组 + 守卫
// ============================================
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Triangle(f64, f64, f64),
}
fn describe_shape(shape: &Shape) -> String {
match shape {
Shape::Circle(radius) if *radius > 10.0 => {
format!("大圆(半径 {:.1})", radius)
}
Shape::Circle(radius) => {
format!("小圆(半径 {:.1})", radius)
}
Shape::Rectangle(w, h) if w == h => {
format!("正方形(边长 {:.1})", w)
}
Shape::Rectangle(w, h) => {
format!("矩形(宽 {:.1},高 {:.1})", w, h)
}
Shape::Triangle(a, b, c) => {
format!("三角形(边 {:.1}, {:.1}, {:.1})", a, b, c)
}
}
}
fn main() {
let shapes = [
Shape::Circle(5.0),
Shape::Circle(15.0),
Shape::Rectangle(4.0, 4.0),
Shape::Rectangle(3.0, 5.0),
Shape::Triangle(3.0, 4.0, 5.0),
];
println!("=== 图形描述 ===");
for shape in &shapes {
println!("{}", describe_shape(shape));
}
println!("\n=== 坐标分类 ===");
let points: [(i32, i32); 4] = [(0, 0), (3, 0), (0, -2), (5, 7)];
for point in points {
let desc = match point {
(0, 0) => "原点".to_string(),
(x, 0) => format!("x 轴上 (x={})", x),
(0, y) => format!("y 轴上 (y={})", y),
(x, y) if x > 0 && y > 0 => format!("第一象限 ({}, {})", x, y),
(x, y) => format!("其他象限 ({}, {})", x, y),
};
println!("点 {:?}: {}", point, desc);
}
}
输出:
TEXT
📖 仅展示
=== 图形描述 ===
小圆(半径 5.0)
大圆(半径 15.0)
正方形(边长 4.0)
矩形(宽 3.0,高 5.0)
三角形(边 3.0, 4.0, 5.0)
=== 坐标分类 ===
点 (0, 0): 原点
点 (3, 0): x 轴上 (x=3)
点 (0, -2): y 轴上 (y=-2)
点 (5, 7): 第一象限 (5, 7)
match 的解构能力可以同时匹配枚举变体、提取内部值、加上守卫条件,实现精确的分类逻辑。编译器的穷尽性检查确保你不会遗漏任何情况。
▶ 示例 5:if let 与 while let 实战(难度 ⭐⭐)
RUST
// ============================================
// if let 和 while let:简化 Option/Result 处理
// ============================================
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
fn main() {
println!("=== if let 处理 Option ===");
let results = [divide(10.0, 2.0), divide(8.0, 0.0), divide(15.0, 3.0)];
for result in &results {
if let Some(value) = result {
println!("除法成功: {:.2}", value);
} else {
println!("除法失败: 除数为零");
}
}
println!("\n=== if let 链式处理 ===");
let alice_input: Option<&str> = Some("42");
let bob_input: Option<&str> = None;
if let Some(num_str) = alice_input {
if let Ok(num) = num_str.parse::<i32>() {
println!("Alice 的数字: {}", num);
}
}
if let Some(num_str) = bob_input {
if let Ok(num) = num_str.parse::<i32>() {
println!("Bob 的数字: {}", num);
}
} else {
println!("Bob 未输入数据");
}
println!("\n=== while let 模拟迭代 ===");
let mut stack = vec![3, 2, 1];
while let Some(top) = stack.pop() {
println!("弹出: {}", top);
}
println!("栈已空");
}
输出:
TEXT
📖 仅展示
=== if let 处理 Option ===
除法成功: 5.00
除法失败: 除数为零
除法成功: 5.00
=== if let 链式处理 ===
Alice 的数字: 42
Bob 未输入数据
=== while let 模拟迭代 ===
弹出: 1
弹出: 2
弹出: 3
栈已空
if let适合只关心Some/Ok而忽略其他情况的场景;while let适合循环取出值直到遇到None的场景(如pop()栈操作)。两者都比完整的match更简洁。
❓ 常见问题
Q match 和 switch 有什么区别?
A match 比 switch 强大得多。
Q 为什么 match 穷尽性检查这么重要?
A 因为它是"编译器帮你找 bug"。
Q if let 什么时候用?
A 只关心一种模式时。
Q match 的 _ 通配符和变量名有什么区别?
A _ 完全忽略值且不绑定,变量名会绑定值但编译器可能警告未使用。
Q if 表达式两个分支类型不一致怎么办?
A 编译报错。
📖 小节
if/else是表达式(可返回值),所有分支必须同一类型match做模式匹配,编译器强制穷尽性检查- 通配符
_匹配所有剩余值,放在 match 最后 if let是 match 的语法糖,适合"只关心一种模式"的场景- match 守卫(
if条件)让模式匹配更灵活 |操作符让一个分支匹配多个模式
📝 作业
- 难度 ⭐:写一个函数
fn number_to_word(n: i32) -> &'static str,用 match 把数字 0-5 映射为英文单词(zero/one/two/three/four/five),其他数字返回 "unknown"。 - 难度 ⭐⭐:定义枚举
enum TrafficLight { Red, Yellow, Green },用 match 给每种颜色返回对应的"等待时间"(红 30s、黄 3s、绿 45s)。 - 难度 ⭐⭐⭐:写一个函数
fn describe_point(point: (i32, i32)),用 match 处理一个二维坐标点:原点(0,0)输出 "origin"、在 x 轴上(x,0)输出 "on x-axis"、在 y 轴上(0,y)输出 "on y-axis"、其余输出坐标值。