跳过正文

8. 模式匹配:match pattern

·
目录
rust-lang - 这篇文章属于一个选集。
§ 8: 本文

模式匹配 match expression {} 是一个表达式,可用于变量赋值:

  • expression 的结果可以是复杂类型, 如 struct、enum、tuple 等;
  • branch 是逗号分割的 pattern => {statements;},
    • 如果是单条语句,则可以省略大括号,如 pattern => expression,
  • branch 返回值类型必须相同;
enum Direction {
    East,
    West,
    North,
    South,
}

fn main() {
    let dire = Direction::South;
    let result = match dire {
        // println!() 返回 () , 是单条语句,故不需要大括号。
        // 各 match branch 以逗号结尾。
        Direction::East => println!("East"),
        _ => {
            // 也返回 ()
            Ok(1);
        }
    }; // let 赋值语句结尾的分号不能省!

    println!("{result}")
}

// pattern 引入了新变量,可能 shadow 以前同名的变量。
fn main() {
    let age = Some(30);

    if let Some(age) = age {
        // age shadow 前面的同名变量
        assert_eq!(age, 30);
    } // 新 age 变量被 drop

    match age {
        // match 的各子句也会创建新变量,可能会 shadow 以前的同名变量
        Some(age) =>  println!("age is a new variable, it's value is {}", age),
        _ => ()
    }
}

let mut setting_value = Some(5);
let new_setting_value = Some(10);
// tuple 类型的 expression
match (setting_value, new_setting_value) {
    (Some(_), Some(_)) => {
        println!("Can't overwrite an existing customized value");
    }
    // _ 可以匹配任何类型值
    _ => {
        setting_value = new_setting_value;
    }
}

match pattern 语法
#

  1. 字面量100, "asdfa", 'a', true/false
  2. range0..=100, 'a'..='z', b'a'..=b'c';(Rust 1.80 开始支持开区间 exclusive range)
  3. | :分割多个 pattern
  4. _ :匹配任何值;
  5. @ :匹配并定义一个变量,如 [email protected], y@(1|2|3), y@..,匹配后 y 是一个包含匹配值的变量;
  • y: yy@(xx) 是将 y 与 xx 匹配,如果满足,匹配的值被设置给变量 yy,这里 @() 的括号并不是 tuple 语义。
  1. 枚举Some(value), None, Ok(value), Err(err);
  2. 变量name, mut name, ref name, ref mut name, 其中 ref/ref mut 用来指定变量 name 是引用而不获得对象的所有权,可以避免转移兑现所有 权;
  3. tuple(key, value), (r, g, b), (r, g, 12);
  4. array/slice[a, b, c], [a, b, 1],[a, .., b], [a, _, b];
  • 对于 Vec,需要先转换为 slice 再匹配(可以直接匹配 [T], 而不需要 &[T])。
  1. struct :必须列出每一个 field,可以使用 .. 来忽略部分 field;
  2. 匹配引用&value, &(k, v),& 用于匹配表达式结果,value/k/v 是解引用后的值;
  3. guard expression4|5|6 if x < 2,表达式是针对整个 pattern,等效于 (4|5|6) if x < 2
let x = 9;
let message = match x {
    0 | 1  => "not many",
    2 ..= 9 => "a few",
    _      => "lots"
};

struct S(i32, i32);
match S(1, 2) {
    // _ 在 slice/array/tuple 中用于忽略指定位置的元素
    S(z @ 1, _) | S(_, z @ 2) => assert_eq!(z, 1),
    _ => panic!(),
}

// 匹配结构体
let p = Point { x: 0, y: 7 };
match p {
    // 匹配结构体时,可以为 field 指定匹配的值。
    Point { x, y: 0 } => println!("On the x axis at {x}"),
    Point { x: 0, y } => println!("On the y axis at {y}"),
    Point { x, y } => { println!("On neither axis: ({x}, {y})");}
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
fn main() {
    let msg = Message::ChangeColor(0, 160, 255);
    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.");
        }
        // 必须列出并匹配 enum variant 的各个字段值
        Message::Move { x, y } => {
            println!("Move in the x direction {x} and in the y direction {y}");
        }
        Message::Write(text) => {
            println!("Text message: {text}");
        }
        Message::ChangeColor(r, g, b) => {
            println!("Change the color to red {r}, green {g}, and blue {b}",)
        }
    }
}

struct Point {
    x: i32,
    y: i32,
}
let p = Point { x: 3, y: 10};
match p {
    // y 值用来做匹配判断
    Point { x, y: 0 } => println!("On the x axis at {}", x),
    // y: yy@(xx) 是将 y 与 xx 匹配,如果满足,匹配的值被设置给变量 yy,这里 @() 的括号并不是 tuple 语义。
    Point { x: 0..=5, y: yy@ (10 | 20 | 30) } => println!("On the y axis at {}", yy),
    Point { x, y } => println!("On neither axis: ({}, {})", x, y),
}

enum Color {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(Color),
}
fn main() {
    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
    match msg {
        // 多级解构
        Message::ChangeColor(Color::Rgb(r, g, b)) => { println!("Change color to red {r}, green {g}, and blue {b}"); }
        Message::ChangeColor(Color::Hsv(h, s, v)) => { println!("Change color to hue {h}, saturation {s}, value {v}") }
        _ => (),
    }
}

// 匹配 tuple: (xx)
let numbers = (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048);
match numbers {
    // ERROR: pattern 中最多只能包含一个 ..
    // (first, .., 16, .., 1024, last) => {
    //     assert_eq!(first, 2);
    //     assert_eq!(last, 2048);
    // }

    // OK
    (first, .., 1024, last) => {
        assert_eq!(first, 2);
        assert_eq!(last, 2048);
    }

    // OK
    (first, .., last) => {
        assert_eq!(first, 2);
        assert_eq!(last, 2048);
    }
}


// 匹配 Array/Slice: [xx]
let array = [1, -2, 6];
match array {
    [0, second, third] => println!("array[0] = 0, array[1] = {}, array[2] = {}", second, third),
    [1, _, third] => println!( "array[0] = 1, array[2] = {} and array[1] was ignored", third ),
    [-1, second, ..] => println!("array[0] = -1, array[1] = {} and all the other ones were ignored",second ),

    // The code below would not compile
    // [-1, second] => ...

    // 匹配后,tail 是一个包含匹配值的变量。
    [3, second, tail @ ..] => println!( "array[0] = 3, array[1] = {} and the other elements were {:?}", second, tail ),
    [first, middle@ .., last] => println!("array[0] = {}, middle = {:?}, array[2] = {}", first, middle, last),
}

Vec 需要先转换为 Slice 再进行匹配:

let v = vec![1, 2, 3];
// v[..] 结果为 unsize 的 [i32],在变量赋值场景需要 &[i32], 但可以用于模式匹配场景
match v[..] {
    [a, b] => { /* this arm will not apply because the length doesn't match */ }
    [a, b, c] => { /* this arm will apply */ }
    _ => { /* this wildcard is required, since the length is not known statically */ }
};

.. :

  • 匹配 array/tuple/slice 值时,使用 .. 来省略任意数量的元素;
  • 对于 struct,使用 .. 来省略未列出的 fields,且只能位于最后;
  • 只能使用一次 ..;
struct Foo {
    x: (u32, u32),
    y: u32,
}
let foo = Foo { x: (1, 2), y: 3 };
match foo {
    Foo { x: (1, b), y } => println!("First of x is 1, b = {},  y = {} ", b, y),
    // field 顺序无关
    Foo { y: 2, x: i } => println!("y is 2, i = {:?}", i),
    // 忽略未列出的其它 field,必须位于最后
    Foo { y, .. } => println!("y = {}, we don't care about x", y),

    // 错误:未列出 field `x`
    //Foo { y } => println!("y = {}", y),
}

更复杂的例子:

match http_response {
    // struct 匹配
    HttpResponse {
        status: 200..=299, // 对 field 值进行匹配
        headers,
        body: Some(ref data), // data 是 &T借用
        ..
    } if headers // 对匹配的 field 进一步条件判断
        .get("content-type")
        .is_some_and(|ct| ct.starts_with("application/json")) =>
    {
        process_json_success(data);
    }
    ref resp @ HttpResponse {
        status: 300 | 301 | 302 | 303 | 307 | 308,
        ..
    } => {
        handle_redirect(resp);
    }
    HttpResponse {
        status: 404 | 410, ..
    } => {
        handle_not_found();
    }
    _ => handle_error(),
}

使用 pattern 解构的场景
#

  1. let 赋值解构:tuple/slice/struct/enum 等复杂数据类型值的赋值解构;
  2. 传参赋值:函数、方法、闭包等的传参赋值;
  3. 表达式:if-let,while-let,for-in,matches!();

赋值析构生成的变量名 scope 是所在 block,新的变量名 by-ref/by-mov/by-copy 对应的值:

  • 可失败模式:if-let/while-let/match/matches!
  • 不可失败模式:变量赋值(let)、函数传参、for 循环解构;
    • 对于变量赋值解构(let),如果指定了 else 语句则也是可失败模式。
let faa = Foo { x: (1, 2), y: 3 };
// 变量赋值和函数传参,使用 match pattern 进行解构,是不能失败的匹配。
// x0 或 y0 是创建的新变量。
let Foo { x : x0, y: y0 } = faa;
let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });

// 变量赋值解构的变量在所在 block 可见
let Some(caps) = re.captures(hay) else { return };
assert_eq!("J", &caps[1]);

// 解构时使用 _ 来忽略特定字段
let (_, right) = slice.split_at(middle);

fn print_number(n: Number) {
    // if-let 可以和 else、else if、else if let 混合使用
    if let Number { odd: true, value } = n {
        println!("Odd number: {}", value);
    } else if let Number { odd: false, value } = n {
        println!("Even number: {}", value);
    }
}

fn print_number(n: Number) {
    match n {
        Number { odd: true, value } => println!("Odd number: {}", value),
        Number { odd: false, value } => println!("Even number: {}", value),
    }
}

fn print_number(n: Number) {
    match n {
        Number { value: 1, .. } => println!("One"),
        Number { value: 2, .. } => println!("Two"),
        Number { value, .. } => println!("{}", value),
        // if that last arm didn't exist, we would get a compile-time error
    }
}

// 解构嵌套 struct
struct Bar { foo: Foo,}
let bar = Bar { foo: faa };
// x: nested_x 的 x 为 field name,而 nested_x 为解构后的变量名,
// 可以省略 nested_x,这时解构后的变量名和 filed name 同名,都是 x;
let Bar { foo: Foo { x: nested_x, y: nested_y } } = bar;
println!("Nested: nested_x = {nested_x:?}, nested_y = {nested_y:?}");

let v = Vec2 { x: 3.0, y: 6.0 };
let Vec2 { x, y } = v;
// `x` is now 3.0, `y` is now `6.0`

let Vec2 { x, .. } = v;
// this throws away `v.y`

#[derive(PartialEq, PartialOrd)]
struct Centimeters(f64);

#[derive(Debug)]
struct Inches(i32);

impl Inches {
    fn to_centimeters(&self) -> Centimeters {
        // let 析构是不可失败模式,& 匹配引用类型,inches 是 i32 类型值。
        // 对元组借用的匹配需要在外层进行,而不能是 Inches(&inches)(enum 也是类似的在外层匹配借用)
        let &Inches(inches) = self;
        Centimeters(inches as f64 * 2.54)
    }
}

// 函数参数使用 pattern 进行赋值解构, 元组引用的匹配需要在外层进行。
fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}

// 参数名称可以是 _
fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {}", y);
}

解构对象时的 by-move 或 by-refer
#

ref/ref mut:解构时,如果匹配值没有实现 Copy trait,则默认是转移所有权到解构后的新变量,通过指定 refref mut 则新变量是 引用匹配 值类型。

在解构 struct/tuple/enum 对象时, 新的变量可以 by-move(默认)或 by-refer 对象:

  • by-move :struct/tuple 的 field 被部分转移,这些 field 不能再访问,但未被转移的字段还是可以访问的。

  • by-refer :需要在标识符前添加 ref/ref mut 修饰符号,表示是借用对象,对应的变量是 &/&mut 类型(这时该 struct/tuple 对象是部分借用状态)。

let (a, b ) = &(1, 2);  // a 和 b 都是 &i32 类型
let &(c, d ) = &(1, 2); // c 和 d 都是 i32 类型
let (&c, d ) = &(1, 2); // 错误

// 在 c 已经是借用类型的情况下,加不加 ref 没有区别。
let (ref c, d) = &(1, 2); // OK,这里 c 前面的 ref 加不加没有区别,c 和 d 都是 &i32 类型。
println!("{}", type_name_of_val(&c));
// 但是,这里有区别
let (ref c, d) = (1, 2); // c 是 &i32 类型,d 是 i32 类型
let (c, d) = (1, 2); // c 和 d 都是 i32 类型

let value = 5;
let mut mut_value = 6;
match value {
    // 匹配后,r 是 value 的引用类型
    ref r => println!("Got a reference to a value: {:?}", r),
}

match mut_value {
    // 匹配后,m 是 &mut 类型变量
    ref mut m => {
        *m += 10;
        println!("We added 10. `mut_value`: {:?}", m);
    },
}

match a {
    None => (),
    Some(value) => (),  // a 的值被 Copy 或 Moved 到 value 中
}

match a {
    None => (),
    Some(ref value) => (), // value 是 a 值的引用类型
}

// 对 struct 部分成员使用 ref/ref mut,否则在成员类型没有实现 Copy 时,默认是转移值到变量
match struct_value {
    Struct{a: 10, b: 'X', c: false} => (),
    Struct{a: 10, b: 'X', ref c} => (),
    Struct{a: 10, b: 'X', ref mut c} => (),
    Struct{a: 10, b: 'X', c: _} => (),
    Struct{a: _, b: _, c: _} => (),
}

// 也可以在声明变量时指定它为 ref 类型
let ref _is_a_reference = 3;

Vec/Array/Slice 类型不支持元素的部分转移, 元素需要实现 Copy 或者被 std::mem::replace

// https://practice.course.rs/ownership/ownership.html
#[derive(Debug)]
struct Person {
    name: String,
    age: Box<u8>,
}

let person = Person {
    name: String::from("Alice"),
    age: Box::new(20),
};

// `name` is moved out of person, but `age` is referenced
let Person { name, ref age } = person;
println!("The person's age is {}", age);
println!("The person's name is {}", name);

// Error! borrow of partially moved value: `person` partial move occurs
//println!("The person struct is {:?}", person);

// `person` cannot be used but `person.age` can be used as it is not moved
println!("The person's age from person struct is {}", person.age);


// Vec/Array 中的元素不支持部分转移
struct Person {
    name: Option<String>,
    birth: i32
}
let mut composers = Vec::new();
composers.push(
    Person {
        name: Some("Palestrina".to_string()),
        birth: 1525
    }
);
// Vec/Array 没有实现 Copy 时,不支持 partial-move,所以编译出错
// let first_name = composers[0].name;
let first_name = &composers[0].name; // OK,借用而非 move

&val: 匹配借用,然后将值赋值给 val:

  • let (a, b) = &v; : a 和 b 都是引用类型, 正确!
  • let &(a, b) = &v;: a 和 b 都是 move 语义。

Rust 不允许从借用(&T/&mut T)转移对象或对象内部的对象的所有权(但是对于 struct/enum/tuple filed 允许部分转移), 所以如果对应值没有实现 Copy 则出错;

let reference = &4;
match reference {
    // OK: i32 实现了 Copy
    &val => println!("Got a value via destructuring: {:?}", val),
}

// pattern 中 & 不能用于 field value:
if let Person { name: &person_name, age: 18..=150 } = value { }  // 错误
if let Person {name: ref person_name, age: 18..=150 } = value { } // 正确

注意:

  1. 对于 enum 和 tuple 类型是在枚举 variant 值或 tuple 值外部,而非内部,来匹配 & 或 &mut;
  2. &/&mut 匹配共享引用和可变引用, && 或 &&mut 来匹配间接引用:
let x: &Option<i32> = &Some(3);
// OK: 等效为 Some(ref y), y 的类型是 &i32
if let Some(y) = x {
}
// OK: 在 variant 外指定 &, y 的类型是 i32, y 类型必须实现 Copy
if let &Some(y) = x {
}
// ERROR: 不能在 variant 内指定 &:expected `i32`, found `&_`
if let Some(&y) = x {
}

fn printf_hellov2(say_hello: Option<&Box<dyn MyTrait + Send + 'static>>) {
    // 错误:枚举需要在外部而非内部来匹配 & 或 &mut
    // error[E0507]: cannot move out of `*say_hello` as enum variant `Some` which is behind a shared reference
    // if let Some(&my_trait) = say_hello {

    // 正确:
    if let Some(my_trait) = say_hello {
        my_trait.say_hello();
    }
}

enum MyEnum {
    A { name: String, x: u8 },
    B { name: String },
}

fn a_to_b(e: &mut MyEnum) {
    // &mut String 类型
    if let MyEnum::A { name, x: 0, } = e {
        // name 是 &mut String 类型。
        // take() 参数类型是 &mut T, 将 name 设置为 T 缺省值,返回 T 值
        *e = MyEnum::B { name: std::mem::take(name), }
    }
    // OK: name 是 String 类型
    if let &mut MyEnum::A { name, x: 0, } = e {
        // ...
    }
}

let (a, b ) = &(1, 2); // a 和 b 都是 &i32 类型
let &(c, d ) = &(1, 2); // c 和 d 都是 i32 类型, c/d 都必须实现 Copy

let (&c, d ) = &(1, 2); // Error
let (ref c, d ) = &(1, 2); // OK

// Rust 字面量也支持引用
let int_reference = &3;
let a = match *int_reference { 0 => "zero", _ => "some" };
let b = match int_reference { &0 => "zero", _ => "some" };
assert_eq!(a, b);

let int_reference = &3;
match int_reference {
    &(0..=5) => (),
    _ => (),
}

如果 T 类型实现实现了 Deref<Target=U>,则 &*T 返回 &U 类型,常用于从智能指针对象返回内部对象类型值:

use std::sync::{Arc, Mutex, Condvar};
use std::thread;

// Mutex 和 Condvar 都具有内部可变性,所以可以通过 pair 和 pair2 的共享引用来进行修改。
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = Arc::clone(&pair);

thread::spawn(move|| {
    // 这里 &*pair2 返回 &(), 赋值解构后, lock 是 &Mutex, cvar 是 &Convar 类型;
    //
    // 不能使用 let &(lock, cvar) = &*pair2; 这会导致 pair2 中的值发生移动(lock 是 Mutex, cvar 是 Convar),
    // 由于 pair2 和 pair 是共享底层的对象, 所以移动时出错。
    let (lock, cvar) = &*pair2;
    // lock.lock() 方法参数是 &self,所以返回的 MutexGuard 其实是包含 lock 对象的引用的智能指针。
    // 这样通过 *started = true 修改的是引用对应的原始对象值。
    let mut started = lock.lock().unwrap();
    *started = true;
    cvar.notify_one();
});

let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
    started = cvar.wait(started).unwrap();
}

pattern match 可能会产生部分转移
#

对于 Vec/Array/Slice 类型对象,共享借用对象的某个元素时,对象整体不能被修改和移动,也不能可变借用、转移其它元素;《== 某个元素借用时,导致整体或其它元素冻结。

对于 Struct/Tuple/Enum 对象,共享借用对象的某个成员或元素时,对象整体不能被修改和移动,但可以可变借用、转移其它成员或元素; 《=== 某个成员或元素的借用时不影响其它成员或元素的可变借用;

rust-borrow

注:上图中的上下级不分元素被染色,对应的是 Struct/Tuple/Enum 类型对象的部分成员或元素。

注意:这里的部分转移针对的都是对象本身,而不是它的借用,因为 Rust 不允许通过借用来转移对象或对象内部子对象的所有权。

  • struct/enum/tuple 的 field 可以被部分转移,但是 Vec/Array/Slice 不能;
  • struct/enum/tuple 被部分转移的字段后续不能再访问,同时 struct 整体也不能被访问, 但是未被部分转移的字段还可以访问;
// https://practice.course.rs/ownership/ownership.html

#[derive(Debug)]
struct Person {
    name: String,
    age: Box<u8>,
}

let person = Person {
    name: String::from("Alice"),
    age: Box::new(20),
};

// name 从 person 中 move,age 引用 person 中 age 值,所以 person 被 partial move。
let Person { name, ref age } = person;

// Error:person 被 partial move 后,不能再整体访问 person:
//println!("The person struct is {:?}", person);

// OK:可以访问 person 未被 move 的字段
println!("The person's age from person struct is {}", person.age);

Vec/Slice/Array 等容器不支持部分转移(如果元素类型实现了 Copy,则不是转移,则 OK),解决办法:

  • 元素 clone();
  • 如果能获得元素的 &mut 引用,可以使用 std::mem:take()/std:mem::replace() 来返回对应元素;

let mut data = vec!["abc".to_string()];
// Error: move occurs because value has type `String`, which does not implement the `Copy` trait
// let e = data[0];

// Vec 不能被解构,需要转换为 &[T] 后才能解构。
// Error: pattern cannot match with input type `Vec<String>`
//let [a] = data;

// Error: move occurs because `a` has type `String`, which does not implement the `Copy`  trait
// if let [a] = data[..] {
//     println!("Results: {a:?}");
// }

// Error: move occurs because `a` has type `String`, which does not implement the `Copy` trait
// if let &[a] = &data[..] {
//     println!("Results: {a:?}");
// }

// OK: a 是 &String 类型, 引用的是 data 中的元素。
if let [a] = &data[..] {
    println!("Results: {a:?}");
}

matches!() 宏
#

matches!(express, pattern) 将 express value 和 pattern 进行匹配,返回 bool 值,可以用在 if 等判断条件场景:

let foo = 'f';
// 注意:matches!() 而非 match!(),因为 match 是关键字。
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));

let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));

match branch 表达式提前返回
#

提前返回的值类型是 !, 而 !可以自动转换为任意类型 ,所以满足各 match branch 返回相同类型值的要求:

use std::num::ParseIntError;

fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
    let first_number = match first_number_str.parse::<i32>() {
        Ok(first_number)  => first_number,
        Err(e) => return Err(e), // branch 提前返回,对应的返回值是 !
    };

    let second_number = match second_number_str.parse::<i32>() {
        Ok(second_number)  => second_number,
        Err(e) => return Err(e),
    };

    Ok(first_number * second_number)
}

fn print(result: Result<i32, ParseIntError>) {
    match result {
        Ok(n)  => println!("n is {}", n),
        Err(e) => println!("Error: {}", e),
    }
}

fn main() {
    print(multiply("10", "2"));
    print(multiply("t", "2"));
}

#[non_exhaustive]
#

non_exhaustive 属性用于表明 struct、enum 的 field 或 variant 是不完整的,后续可能会新加。

non_exhaustive 属性可以应用于 structs, enums 和 enum variants。

使用 non_exhaustive 标记的 structs, enums 和 enum variants 只能在它定义的 crate 中创建,在其它外部 crate 中不能创建标记类型的对象。

// These are types defined in an upstream crate that have been annotated as
// `#[non_exhaustive]`.
use upstream::{Config, Token, Id, Error, Message};

// Cannot construct an instance of `Config`; if new fields were added in
// a new version of `upstream` then this would fail to compile, so it is
// disallowed.
let config = Config { window_width: 640, window_height: 480 };

在外部 crate 中来 match 标记的对象时,为了应对可能后加的 field 和 variat,对于 field 匹配必须包含 …,对于 variat 必须有 _:

// These are types defined in an upstream crate that have been annotated as
// `#[non_exhaustive]`.
use upstream::{Config, Token, Id, Error, Message};

// Cannot match on a non-exhaustive enum without including a wildcard arm.
match error {
  Error::Message(ref s) => { },
  Error::Other => { },
  // would compile with: `_ => {},`
}

// Cannot match on a non-exhaustive struct without a wildcard.
if let Ok(Config { window_width, window_height }) = config {
    // would compile with: `..`
}

// Cannot match a non-exhaustive unit-like or tuple struct except by using
// braced struct syntax with a wildcard.
// This would compile as `let Token { .. } = token;`
let Token = token;
// This would compile as `let Id { 0: id_number, .. } = id;`
let Id(id_number) = id;

match message {
  // Cannot match on a non-exhaustive struct enum variant without including a wildcard.
  Message::Send { from, to, contents } => { },
  // Cannot match on a non-exhaustive tuple or unit enum variant.
  Message::Reaction(type) => { },
  Message::Quit => { },
}

参考:https://doc.rust-lang.org/reference/attributes/type_system.html

rust-lang - 这篇文章属于一个选集。
§ 8: 本文

相关文章

1. 标识符和注释:identify/comment
·
Rust 标识符介绍
10. 泛型和特性:generic/trait
·
Rust 泛型和特性
11. 类型协变:type coercion
·
Rust 高级话题:子类型和类型协变
12. 迭代器:iterator
·
Rust 迭代器