match 太长、只想处理一种情况时,Rust 给了 if let、while let 和 let else。这篇验证它们各自的规则:什么时候必须发散、绑定在哪里生效、编译器怎么提醒你用错了,最后还有一个只有同时编译 2021 与 2024 才会看到的差别 —— 临时值什么时候被释放。
源码:
1 | fn main() {
2 | let a: Option<i32> = Some(7);
3 | let b: Option<i32> = None;
4 | if let Some(x) = a { println!("a 有值 {x}"); }
5 | if let Some(x) = b { println!("b 有值 {x}"); } else { println!("b 是 None"); }
6 | let mut stack = vec![1, 2, 3];
7 | while let Some(top) = stack.pop() { println!("pop {top}"); }
8 | // 2024 edition 可以在 if let 里用 && 连接条件(let chains)
9 | if let Some(x) = a && x > 5 { println!("let chain:a = {x} 且大于 5"); }
10 | }
编译:成功
运行(退出码 0):
a 有值 7
b 是 None
pop 3
pop 2
pop 1
let chain:a = 7 且大于 5与 match 的穷尽匹配相比,if let 不要求列全,所以也不再有编译器的完整性检查。
源码:
1 | fn parse_port(s: &str) -> Option<u16> {
2 | let Ok(p) = s.parse::<u16>() else { return None; }; // 失败就提前返回
3 | Some(p) // 这里还能用 p:绑定留在了外层
4 | }
5 | fn main() {
6 | println!("{:?} {:?} {:?}", parse_port("8080"), parse_port("abc"), parse_port("99999"));
7 | }
编译:成功
运行(退出码 0):
Some(8080) None None
源码:
1 | fn main() {
2 | let o: Option<i32> = Some(1);
3 | let Some(x) = o else { println!("没有值"); };
4 | println!("{x}");
5 | }
编译:失败
error[E0308]: `else` clause of `let...else` does not diverge
--> <实验目录>/src/u9c.rs:3:26
|
3 | let Some(x) = o else { println!("没有值"); };
| ^^^^^^^^^^^^^^^^^^^^^^^ expected `!`, found `()`
|
= note: expected type `!`
found unit type `()`
= help: try adding a diverging expression, such as `return` or `panic!(..)`
= help: ...or use `match` instead of `let...else`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`. 源码:
1 | fn main() {
2 | let o: Option<i32> = Some(1);
3 | let Some(x) = o;
4 | println!("{x}");
5 | }
编译:失败
error[E0005]: refutable pattern in local binding
--> <实验目录>/src/u9d.rs:3:9
|
3 | let Some(x) = o;
| ^^^^^^^ pattern `None` not covered
|
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
= note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html
= note: the matched value is of type `Option<i32>`
help: you might want to use `let...else` to handle the variant that isn't matched
|
3 | let Some(x) = o else { todo!() };
| ++++++++++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0005`.
源码:
1 | fn main() {
2 | let t = (1, 2);
3 | if let (a, b) = t { println!("{a} {b}"); }
4 | }
编译:成功;警告:
warning: irrefutable `if let` pattern
--> <实验目录>/src/u9e.rs:3:8
|
3 | if let (a, b) = t { println!("{a} {b}"); }
| ^^^^^^^^^^^^^^
|
= note: this pattern will always match, so the `if let` is useless
= help: consider replacing the `if let` with a `let`
= note: `#[warn(irrefutable_let_patterns)]` on by default
warning: 1 warning emitted
运行(退出码 0):
1 2 --edition 2021:
源码:
1 | struct D;
2 | impl Drop for D { fn drop(&mut self) { println!(" drop(临时值 D)"); } }
3 | fn f(_: &D) -> Option<i32> { None }
4 | fn main() {
5 | if let Some(_) = f(&D) { println!(" then 分支"); } else { println!(" else 分支"); }
6 | println!(" if let 语句之后");
7 | }
编译:成功
运行(退出码 0):
else 分支
drop(临时值 D)
if let 语句之后
--edition 2024:
编译:成功
运行(退出码 0):
drop(临时值 D)
else 分支
if let 语句之后这个差别理论上会影响条件里持有锁(见Mutex 与 RwLock)或借用的临时值;我这里只用带 Drop 的临时值验证了释放顺序,没有拿 MutexGuard 实测。