跳过正文

模式匹配:match pattern

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

match expression {} 是一个表达式,可以用于变量赋值(各分支返回的值类型必须相同):

  • 子句格式: pattern => {statements;} , 如果是单条语句则可以省略大括号,如 pattern => expression
  • match block 中各子句用逗号分割;(注:函数和闭包的返回值用 -> 分割;)
  • expression 可以返回复杂类型,如 tuple、struct 等,从而实现多个返回值的 pattern 匹配;
enum Direction {
    East,
    West,
    North,
    South,
}

fn main() {
    let dire = Direction::South;

    let result = match dire {
        // println!() 返回 ()
        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);
match (setting_value, new_setting_value) {
    (Some(_), Some(_)) => {
        println!("Can't overwrite an existing customized value");
    }
    _ => {
        setting_value = new_setting_value;
    }
}

println!("setting is {:?}", setting_value);

matches!() 宏 (match 是关键字)将 express value 和 pattern 进行匹配,可用于表达式或条件判断:

let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));

match 表达式提前返回 Err(e),对应的 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 :[a, b, c], [a, b, 1]
  10. slice :[a, b], [a, _, b], [a, .., b]
  11. struct :必须列出每一个 field,可以使用 .. 来忽略部分 field;
  12. 匹配引用 :&value, &(k, v); // & 用于匹配表达式结果, value/k/v 都代表解了一层引用后的值;
  13. guard expression :4|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}",)
        }
    }
}


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}")
        }
        _ => (),
    }
}

pattern .. 用法:

  • 匹配 array/tuple/slice 值时,可以使用 .. 来省略任意数量的元素;
  • 对于 struct,使用 .. 来省略未列出的 field,且只能位于最后;
  • 只能使用一次 ..;
  • Vec 需要转换为 Slice 后才能解构;
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),
}

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),
}


let num = Some(4);
let split = 5;
match num {
    // if match guard
    Some(x) if num < split => assert!(x < split),
    Some(x) => assert!(x >= split),
    None => (),
}

使用 pattern 解构的场景:

  1. let 赋值:tuple/slice/struct/enum 等复杂数据类型值的 赋值解构 场景:
  2. 函数或方法或闭包的参数赋值,它们在传参时其实是赋值解构。
  3. 表达式:if-let,while-let,for,match
// (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 */ }
};

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

  • if-let/while-let/match/matches! 匹配场景是 可失败模式
  • 变量赋值解构(let)、函数参数解构、for 循环解构是 不可失败模式
// 变量赋值和函数传参,是使用 match pattern 进行解构,且必须是不能失败的匹配。
let faa = Foo { x: (1, 2), y: 3 };
let Foo { x : x0, y: y0 } = faa;

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]);

// 解构嵌套 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:?}");

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

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

impl Inches {
    fn to_centimeters(&self) -> Centimeters {
        // let 析构是不可失败模式,& 匹配引用类型,inches 是 i32 类型值。
        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);
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
    foo(3, 4);
}

ref 和 ref mut:先匹配,然后创建借用类型的变量:

  • 先匹配再绑定, 绑定时默认是 copy 或 move, 通过指定 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);
    },
}

let ref _is_a_reference = 3;

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: _} => (),
}

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

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

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

  • let (a, b) = &v; 这时 a 和 b 都是引用类型, 正确!
  • let &(a, b) = &v; 这时 a 和 b 都是 move 语义. Rust 不允许从引用类型(不管是共享还是可变)值 move 其中的内容 , 所以如果对应值没有实现 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 类型是在枚举 variant 值外部而非内部来匹配 & 或 &mut;
  2. 对于 tuple 类型, 也是在 tuple 外部匹配 & 或 &mut;
  3. &/&mut 匹配共享引用和可变引用, && 或 &&mut 来匹配间接引用: Reference patterns dereference the pointers that are being matched and, thus, borrow them.
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 {
}


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) => (),
    _ => (),
}

对于实现了 Deref<Target=U> 的类型 T 值, &*T 返回 &U 类型:

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

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 可能会产生 partial move:

  • struct/enum 的 field 可以被 partial move,但是 Vec/Array 不能;
  • struct/enum 被 partial move 的字段后续不能再访问,同时 struct 整体也不能被访问, 但是未被 partial move 的字段还可以访问;
// 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 等容器不支持 partial move(如果元素实现了 Copy,则不是 move),解决办法:

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

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

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

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

// 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 - 这篇文章属于一个选集。
§ 9: 本文

相关文章

不安全:unsafe
··824 字
Rust
Rust
借用:refer/borrow
··3120 字
Rust
Rust 引用类型和借用
内置类型:type
··16432 字
Rust
Rust 内置基本类型介绍
函数、方法和闭包:function/method/closure
··6964 字
Rust
Rust 函数、方法和闭包