bigywater.tech
Rust · 8

match 穷尽匹配

match 的「穷尽」是 Rust 最实用的安全网之一:编译器替你检查所有情况都处理了。这篇逐条验证它到底查什么、报错信息长什么样、模式有哪些写法,以及一个常被忽视的取舍 —— 用 _ 通配符会让这张安全网在你新增枚举变体时失效。

E0004
漏掉变体/数值时的错误码,并附修复建议
_ 的代价
新增枚举变体时,带 _ 的 match 不报错、显式 match 报错
u8::MAX
对 u8 写 0..=254 时编译器指出漏掉的值

穷尽性检查

实测 · 本机执行输出
  源码:
      1 | enum Dir { North, East, South, West }
      2 | fn turn(d: Dir) -> &'static str {
      3 |     match d {
      4 |         Dir::North => "向北",
      5 |         Dir::East => "向东",
      6 |     }
      7 | }
      8 | fn main() { println!("{}", turn(Dir::South)); }
  编译:失败
    error[E0004]: non-exhaustive patterns: `Dir::South` and `Dir::West` not covered
     --> <实验目录>/src/u8a.rs:3:11
      |
    3 |     match d {
      |           ^ patterns `Dir::South` and `Dir::West` not covered
      |
    note: `Dir` defined here
     --> <实验目录>/src/u8a.rs:1:6
      |
    1 | enum Dir { North, East, South, West }
      |      ^^^                -----  ---- not covered
      |                         |
      |                         not covered
      = note: the matched value is of type `Dir`
    help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
      |
    5 ~         Dir::East => "向东",
    6 ~         Dir::South | Dir::West => todo!(),
      |
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0004`.

模式的写法

实测 · 本机执行输出
  源码:
      1 | #[derive(Debug)] enum Msg { Quit, Move { x: i32, y: i32 }, Write(String) }
      2 | fn classify(n: u32) -> &'static str {
      3 |     match n { 0 => "零", 1 | 2 | 3 => "很小", 4..=9 => "个位数", 10..=99 => "两位数", _ => "更大" }
      4 | }
      5 | fn main() {
      6 |     for n in [0, 2, 7, 42, 1000] { println!("{n} → {}", classify(n)); }
      7 |     let m = [Msg::Quit, Msg::Move { x: 3, y: -1 }, Msg::Write("hi".into())];
      8 |     for x in &m {
      9 |         let s = match x { Msg::Quit => "退出".to_string(), Msg::Move { x, y: 0 } => format!("水平移动 {x}"), Msg::Move { x, y } if *y < 0 => format!("向下 {y},横向 {x}"), Msg::Move { .. } => "其它移动".to_string(), Msg::Write(t) => format!("写入 {t}") };
     10 |         println!("{:?} → {s}", x);
     11 |     }
     12 |     let age = 25;
     13 |     match age { n @ 18..=64 => println!("成年人,年龄 {n}"), n => println!("其它 {n}") }
     14 |     match (3, -3) { (x, y) if x + y == 0 => println!("元组:和为零"), (x, _) if x % 2 == 1 => println!("x 是奇数"), _ => println!("都不是") }
     15 | }
  编译:成功
  运行(退出码 0):
    0 → 零
    2 → 很小
    7 → 个位数
    42 → 两位数
    1000 → 更大
    Quit → 退出
    Move { x: 3, y: -1 } → 向下 -1,横向 3
    Write("hi") → 写入 hi
    成年人,年龄 25
    元组:和为零

更紧凑的单模式写法见if let 与 let else。

_ 与枚举演进

实测 · 本机执行输出
  (1) 显式列全每个变体的 match,Level 只有 Low、High:
  编译:成功
  运行(退出码 0):
    2
  (2) 给 Level 新增变体 Critical,显式 match 一行不改:
  编译:失败
    error[E0004]: non-exhaustive patterns: `Level::Critical` not covered
     --> <实验目录>/src/u8d.rs:2:30
      |
    2 | fn f(l: Level) -> u8 { match l { Level::Low => 1, Level::High => 2 } }
      |                              ^ pattern `Level::Critical` not covered
      |
    note: `Level` defined here
     --> <实验目录>/src/u8d.rs:1:23
      |
    1 | #[derive(Debug)] enum Level { Low, High, Critical }
      |                       ^^^^^              -------- not covered
      = note: the matched value is of type `Level`
    help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
      |
    2 | fn f(l: Level) -> u8 { match l { Level::Low => 1, Level::High => 2, Level::Critical => todo!() } }
      |                                                                   ++++++++++++++++++++++++++++
    
    error: aborting due to 1 previous error
    …(共 20 行)
  (3) 同样新增 Critical,但 match 里用了 _ 通配:
  编译:成功
  运行(退出码 0):
    2

对「别人的、会增长的」枚举(标了 #[non_exhaustive] 的)你反而必须写 _;这个属性我没有在这里验证。

分支类型与整数穷尽

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let n = 3;
      3 |     let s = match n { 1 => "one", 2 => 2, _ => "many" };
      4 |     println!("{}", s);
      5 | }
  编译:失败
    error[E0308]: `match` arms have incompatible types
     --> <实验目录>/src/u8e.rs:3:40
      |
    3 |     let s = match n { 1 => "one", 2 => 2, _ => "many" };
      |             -------        -----       ^ expected `&str`, found integer
      |             |              |
      |             |              this is found to be of type `&str`
      |             `match` arms have incompatible types
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0308`.
  源码:
      1 | fn main() {
      2 |     let x: u8 = std::env::args().count() as u8;
      3 |     let s = match x { 0..=254 => "不是 255" };
      4 |     println!("{s}");
      5 | }
  编译:失败
    error[E0004]: non-exhaustive patterns: `u8::MAX` not covered
     --> <实验目录>/src/u8f.rs:3:19
      |
    3 |     let s = match x { 0..=254 => "不是 255" };
      |                   ^ pattern `u8::MAX` not covered
      |
      = note: the matched value is of type `u8`
    help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
      |
    3 |     let s = match x { 0..=254 => "不是 255", u8::MAX => todo!() };
      |                                            ++++++++++++++++++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0004`.

分步演示

match 的穷尽匹配
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。错误信息是原样的 rustc 1.98.1 输出
    • 没有验证:#[non_exhaustive]、match 在 release 下的代码生成(跳转表还是比较链)、or-pattern 在嵌套位置的限制、对引用与 Box 的 match 的自动解引用(match ergonomics)、字符串的 match
    京ICP备2026067502号-1京公网安备11010502063487号