bigywater.tech
Rust · 9

if let 与 let else

match 太长、只想处理一种情况时,Rust 给了 if let、while let 和 let else。这篇验证它们各自的规则:什么时候必须发散、绑定在哪里生效、编译器怎么提醒你用错了,最后还有一个只有同时编译 2021 与 2024 才会看到的差别 —— 临时值什么时候被释放。

E0308
let else 的 else 块不发散时的错误
E0005
普通 let 用可能失败的模式时的错误
2021 ≠ 2024
if let 条件里的临时值:2021 在 else 之后释放,2024 在进入 else 之前释放

if let 与 while let

实测 · 本机执行输出
  源码:
      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 不要求列全,所以也不再有编译器的完整性检查。

let else

实测 · 本机执行输出
  源码:
      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`.

refutable 与 irrefutable

实测 · 本机执行输出
  源码:
      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 2024 里临时值的变化

实测 · 本机执行输出
  --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 实测。

分步演示

if let 与 let else
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。let chains 需要 edition 2024 与较新的 rustc(本机 1.98);第 4 节分别用 --edition=2021 与 --edition=2024 编译同一份源码
    • 没有验证:if let 临时值变化对 MutexGuard 的实际影响、matches! 宏、let else 与 ? 运算符的组合
    京ICP备2026067502号-1京公网安备11010502063487号