跳过正文

8. 模式匹配:match pattern

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

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

  • expression 结果可以是各种复杂类型, 如 struct、enum 等;
  • 各 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;
    }
}

matches!(express, pattern) 宏 (match 是关键字)将 express value 和 pattern 进行匹配,返回 bool 值:

let foo = 'f';
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"));
}

match pattern 语法:

  1. 字面量 :100,字符串,bool,char;
  2. range :0..=100, ‘a’..=‘z’, b’a’..=b’c’;(Rust 1.80 开始支持 exclusive range)
  3. | :分割多个 pattern;
  4. _ :匹配任何值;
  5. @ :匹配并定义一个变量,如 [email protected], y@(1|2|3), y@..,匹配后 y 是一个包含匹配值的变量;
  6. 枚举Some(value), None, Ok(value), Err(err);
  7. 变量name, mut name, ref name, ref mut name, 其中 ref/mut 用来指定变量 name 是引用类型(不会有所有权转移);
  8. tuple(key, value), (r, g, b), (r, g, 12);
  9. 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 {
    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
    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}") }
        _ => (),
    }
}

// (xx) 匹配 tuple
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);
    }
}


// [xx] 匹配 Array/Slice
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];
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),
}

使用 pattern 解构的场景:

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

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

  • if-let/while-let/match/matches! 的析构匹配时可失败模式
  • 变量赋值解构(let)、函数传参、for 循环解构是不可失败模式
  • 但 let 赋值支持析构失败时的 else 语句;
// 变量赋值和函数传参,使用 match pattern 进行解构,是不能失败的匹配。
let faa = Foo { x: (1, 2), y: 3 };
let Foo { x : x0, y: y0 } = faa; // x0 或 y0 是创建的新变量。

let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
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 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 };
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);
}

ref/ref mut:析构时是先匹配再定义变量, 值如果没有实现 Copy 则默认是转移到变量, 通过指定 ref 或 ref mut,表示变量是引用或可变引用类型:

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;

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

  • let (a, b) = &v; : a 和 b 都是引用类型, 正确!
  • let &(a, b) = &v;: a 和 b 都是 move 语义。
    • Rust 不允许从借用(不管是 &T 还是 &mut T)转移内部对象, 所以如果对应值没有实现 Copy 则出错;
let reference = &4;
match reference {
    &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>>) {
    // 错误:
    // 1. 不能通过借用(共享或可变)来转移对象,my_trait 是 Box<dyn MyTrait + Send + 'static> 类型,转移了 Box 对象所有权
    // 2. 对于 enum ,需要在外部而非内部排匹配 & 或 &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 类型,常用于从智能指针类型值,如 Box、Rc、Arc 等返回内部的对象类型:

  • 对于泛型函数参数, Rust 传参时不会进行 type coercion 或自动 Deref 来满足 trait bound 的要求,这时需要使用 &*T;
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;
    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 可能会产生部分转移:

  • 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,则不是转移),解决办法:

  • 元素 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:?}");
}
rust-lang - 这篇文章属于一个选集。
§ 8: 本文

相关文章

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