跳转到内容

Rust if let 和 while let

你已经用 match 处理过 Option 和 Result。不过大多数时候,你真正关心的其实只有一种模式,其余的都想忽略。为了什么都不做而写一个完整的 match,再加上一个 _ => {} 分支,也能用,但对这种场景来说未免太隆重了。if let 就是为此准备的捷径。

fn main() {
let age: Option<i32> = Some(30);
match age {
Some(value) => println!("年龄是 {value}"),
_ => {}, // 其实我们并不关心这种情况
}
}

那个 _ => {} 什么也没做,只是为了满足 match“每种情况都必须处理”的规则。如果你只想在 Some 时做点事,这段代码就比它要表达的意思多出了不少。

fn main() {
let age: Option<i32> = Some(30);
if let Some(value) = age {
println!("年龄是 {value}");
}
}

输出:

年龄是 30
▶ 动手试一试

可以这样读:“如果 age 能匹配模式 Some(value),就执行这个代码块,value 绑定到里面的值。”如果 age 是 None,这个代码块就直接跳过——不报错,也不打印任何东西。

if let 也可以带 else,和普通的 if 一样:

fn main() {
let age: Option<i32> = None;
if let Some(value) = age {
println!("年龄是 {value}");
} else {
println!("没有提供年龄");
}
}
▶ 动手试一试

对 Result 也是同样的用法,你在 Result 那一页已经见过它了:

fn main() {
let input = "42";
if let Ok(number) = input.parse::<i32>() {
println!("解析结果:{number}");
} else {
println!("无法解析");
}
}
▶ 动手试一试

就像 if 有 else if 一样,if let 后面也可以接 else if let。Rust 会依次尝试每个模式,执行第一个匹配成功的代码块。如果你要按优先顺序在几个地方找一个值,这种写法就很方便:

fn main() {
let nickname: Option<&str> = None;
let full_name: Option<&str> = Some("Ferris Crab");
if let Some(nick) = nickname {
println!("嗨,{nick}!");
} else if let Some(name) = full_name {
println!("你好,{name}。");
} else {
println!("你好,陌生人。");
}
}

输出:

你好,Ferris Crab。
▶ 动手试一试

nickname 是 None,所以第一个代码块被跳过。full_name 匹配 Some(name),于是执行这个代码块,最后的 else 根本不会走到。你还可以在这条链的任意位置混入普通条件,比如 else if is_admin { ... }。

每个变量只在自己的代码块里有效:nick 不能在第二个代码块里使用,name 在整条链结束后也用不了。如果后面的代码都需要用到拆出来的值,可以看看模式匹配那一页的 let-else。

while let 把同样的思路用在循环上:只要值还能匹配模式,就一直循环下去。用来把一个集合逐个取空时特别顺手:

fn main() {
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{top}");
}
}

输出:

3
2
1
▶ 动手试一试

只要还有元素,.pop() 就返回 Some(value);vector 空了之后就返回 None——这时 while let 会自动结束循环,不需要你手动检查长度。

如果真的需要分别处理多种情况,就用 match。如果你只关心一种模式,其余的都可以直接忽略,就用 if let。它并不是用来取代 match 的,只是“我只关心一个分支”这种常见情况的简短写法。

从 let mut numbers = vec![4, 8, 15]; 和一个初始为 0 的累加变量 total 开始。用 while let 配合 .pop(),每次从末尾取出一个数,加到 total 上,并打印取出的数和新的总和。提示:total 需要 mut,total += n 就是把 n 加上去。

查看答案
fn main() {
let mut numbers = vec![4, 8, 15];
let mut total = 0;
while let Some(n) = numbers.pop() {
total += n;
println!("取出 {n},现在总和是 {total}");
}
}

输出:

取出 15,现在总和是 15
取出 8,现在总和是 23
取出 4,现在总和是 27
▶ 动手试一试

.pop() 是从末尾取元素的,所以 15 最先出来。

写下 let input = "17";。如果它能解析成 u32,就打印这个年龄是成年人(18 岁及以上)还是未成年人;如果解析不了,就打印它不是数字。也试试把 input 改成 "abc"。提示:在 if let 代码块里面再写一个普通的 if / else。

查看答案
fn main() {
let input = "17";
if let Ok(age) = input.parse::<u32>() {
if age >= 18 {
println!("{age} 岁是成年人");
} else {
println!("{age} 岁是未成年人");
}
} else {
println!("“{input}”不是数字");
}
}

输出:

17 岁是未成年人
▶ 动手试一试

如果 input = "abc",解析会失败,于是执行外层的 else,打印 “abc”不是数字。

  • if let Pattern = value { ... } 只在 value 匹配 Pattern 时执行代码块,其他情况都会悄悄跳过。
  • 加上 else 就能处理不匹配的情况,和普通的 if 一样;还可以用 else if let 把几个模式串起来。
  • while let Pattern = value { ... } 只要值还能匹配就一直循环,很适合配合 .pop() 把集合取空。
  • 只关心一种模式时用 if let/while let;需要分别处理好几种情况时用 match。

小测验

1. 运行 if let Some(value) = age 时,如果 age 是 None,会发生什么?

2. 什么时候应该用 match 而不是 if let?

3. 在 while let Some(top) = stack.pop() { ... } 中,循环什么时候停止?

得分: 0 / 3