30_rust_模式匹配

发布时间 2023-11-28 20:51:24作者: 00lab

模式匹配

模式:是rust中的一种特殊语法,用于匹配复杂和简单类型的结构。
将模式与匹配表达式和其他结构结合使用,可更好控制程序控制流。
模式由以下元素(及组合)组成:

  • 字面值
  • 解构的数组、enum、struct和tuple
  • 变量
  • 通配符
  • 占位符

模式匹配的场景

match的Arm(分支)

格式:

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

match表达式的要求:详尽,分支需要包含所有的可能性。
一个特殊模式:_(下划线)

  • 会匹配任何内容
  • 不会绑定到变量
  • 常用于match的最后一个arm,或用于忽略某些值

match类似于swith语句,下划线类似于swith里的default语句。

条件if let表达式

if let表达式主要作为一种简短的方式来等价代替只有一个匹配项的match。
if let可选的跟随语句,可拥有else,包括:

  • else if
  • else if let

和match不同的是,if let不会检查穷尽性。

fn main() {
    let f: Option<&str> = None;
    let b = false;
    let a: Result<u8, _> = "5".parse();
    if let Some(c) = f {
        println!("c={}", c);
    } else if b {
        println!("b={}", b);
    } else if let Ok(d) = a {
        if d > 3 {
            println!("a > 3");
        } else {
            println!("a <= 3");
        }
    } else {
        println!("other");
    }
}

while let条件循环

只要模式继续满足匹配条件,则允许while循环一直运行。

fn main() {
    let mut s = Vec::new();
    s.push(1);
    s.push(2);
    s.push(3);
    while let Some(t) = s.pop() {
        println!("{}", t);
    }
}

for循环

for循环是rust中最常见的循环,for循环中,模式就是紧随for关键字后的值。

fn main() {
    let vs = vec!['a', 'b', 'c'];
    for (i, v) in vs.iter().enumerate() {
        println!("{},{}", i, v);
    }
}
/*
0,a
1,b
2,c
*/

let语句

let语句也是模式,使用let PATTERN = EXPRESSION;

let a = 5; // 这种也是
let (x, y, z) = (1, 2, 3);
let (c, d) = (5, 6, 7); // 报错,个数不匹配

函数参数

fn fun1(x: i32) {}
fn fun2(&(x, y): &(i32, i32)) {
  println!("{}, {}", x, y);
}
fn main() {
  let p = (2, 3);
  fun2(&p);
}

可辩驳性:模式是否会无法匹配

模式有两种:可失败的、不可失败的(或者叫 可辩驳的、无可辩驳的)

  • 无可辩驳的:能匹配任何可能传递的值的模式,如let x=5;,肯定不会失败。
  • 可辩驳的: 对于某些可能的值,无法进行匹配的模式,如if let Some(x) = val; 如果val为None时就无法匹配上。

语法分类:

  • 函数参数、let语句、for循环只接受无可辩驳的模式
  • if let和while let接受可辩驳和无可辩驳的模式,当接收无可辩驳的时候会发出警告,因可能失败
fn main() {
    let a: Option<i32> = Some(5);
    let Some(a) = a; // 编译报错refutable pattern in local binding,`let` bindings require an "irrefutable pattern"
}

意思就是let只能绑定到无可辩驳模式,但后面的Some存在可辩驳性。

fn main() {
    let a: Option<i32> = Some(5);
    if let Some(a) = a {} // 改成可辩驳模式,if let是可失败的
}

如果改成:

fn main() {
    let a: Option<i32> = Some(5);
    if let x = 5 {} 
    // 编译警告irrefutable `if let` pattern,
    // this pattern will always match, so the `if let` is useless,
    // consider replacing the `if let` with a `let`
}

无可辩驳的if let模式,这个模式将永远匹配,所以if let是没有用的,可以考虑用let替换if let
对于match表达式,最后一个表达式应是无可辩驳的,其余表达式是可辩驳的。

模式语法

匹配字面值

模式可直接匹配字面值

fn main() {
    let x = 2;
    match x {
        1 => println!("1"),
        2 => println!("2"),
        _ => println!("other"),
    }
}

匹配命名变量

命名变量是可匹配任何值的无可辩驳模式

fn main() {
    let x = Some(3);
    let y = 10;
    match x {
        Some(5) => println!("x=5"),
        Some(y) => println!("in match y={:?}", y),
        _ => println!("default {:?}", x),
    }
    println!("go out match y={:?}", y);
}
/*输出
in match y=3
go out match y=10
*/

多重模式

在match表达式中,使用|语法(或的意思)可匹配多种模式。

fn main() {
    let x = 2;
    match x {
        1 | 2 => println!("1 or 2"), //表示1或2都能匹配上
        3 => println!("3"),
        _ => println!("other"),
    }
}

使用..=匹配某个范围值

fn main() {
    let x = 5;
    match x {
        1..=5 => println!("1 ~ 5"), //表示匹配1到5任意数字
        _ => println!("other"),
    }
    let x = 'C';
    match x {
        'a'..='z' => println!("a ~ z"), //表示匹配a到z
        'A'..='Z' => println!("A ~ Z"), //表示匹配A到Z
        _ => println!("other"),
    }
}
/*输出
1 ~ 5
A ~ Z*/

解构以分解值

可用模式来解构struct、enum、tuple,从而引用这些类型值的不同部分。

struct P { x: i32, y: i32, }
fn main() {
    let p1 = P { x: 2, y: 3, };
    let P { x: a, y: b} = p1; //创建a、b变量分别匹配x和y
    println!("{},{}", a, b);
    // 但上述写法比较冗余,可改成如下创建x y变量的写法
    let P {x, y} = p1; // 同名后能够直接匹配,等同于 let P { x: x, y: y} = p1;
    println!("{},{}", x, y);
    // 还可使用match匹配
    match p1 {
        P { x, y: 0 } => println!("match x=any y=0, {},{}", x, y),// 匹配x任意,y=0
        P { x: 0, y } => println!("match x=0 y=any, {},{}", x, y),// 匹配x=0,y任意
        P { x, y } => println!("match x & y =any, {},{}", x, y),// 匹配x和y都任意
    }
}
/*输出
2,3
2,3
match x & y =any, 2,3
*/

解构enum

enum Msg {
    Q,
    M { x: i32, y: i32, },
    W (String),
    C (i32, i32, i32),
}
fn main() {
    let m = Msg::C(0, 1, 2);
    match m {
        Msg::Q => { println!("Q") },
        Msg::M { x, y } => { println!("{},{}", x, y) },
        Msg::W(t) => { println!("{}", t) },
        Msg::C(a, b, c) => { println!("{},{},{}", a, b, c) },
    }
}

解构嵌套的struct和enum

enum Cc {
    C1 (i32, i32, i32),
    C2 (i32, i32, i32),
}
enum Msg {
    Q,
    M { x: i32, y: i32, },
    W (String),
    C (Cc),
}
fn main() {
    let m = Msg::C(Cc::C2(0, 1, 2));
    match m {
        Msg::Q => { println!("Q") },
        Msg::M { x, y } => { println!("{},{}", x, y) },
        Msg::W(t) => { println!("{}", t) },
        Msg::C(Cc::C1(a, b, c)) => { println!("C1 {},{},{}", a, b, c) },
        Msg::C(Cc::C2(a, b, c)) => { println!("C2 {},{},{}", a, b, c) },
    }
}
//输出C2 0,1,2

解构struct和tuple

struct P { x: i32, y: i32, }
fn main() {
    let ((f1, f2), P {x, y}) = ((1, 2), P {x: 3, y: 5, });
    println!("{},{}, {},{}", f1, f2, x, y);//1,2, 3,5
}

在模式中忽略值

有几种可在模式中忽略整个值或部分值:__配合其它模式使用以_开头的名称..忽略值的剩余部分

  • 使用_忽略整个值
fn func(_: i32, y: i32) {
    println!("{}", y);
}
fn main() {
    func(2, 3);
}
  • 使用嵌套的_忽略值的一部分
fn main() {
    let mut a = Some(2);
    let b = Some(3);
    match (a, b) {
        (Some(_), Some(_)) => { //匹配只要两个都是Some即可,内部的值并不关心
            println!("all Some")
        }
        _ => { a = b; }
    }
    println!("a={:?}", a);
    let v = (1, 2, 3, 4, 5);
    match v { // 只匹配第1 3 5号元素
        (n1, _, n3, _, n5) => println!("{},{},{}", n1, n3, n5),
    }
}
/*
all Some
a=Some(2)
1,3,5
*/
  • 使用_开头命名忽略未使用的变量
fn main() {
    let _x = 5; // 未使用变量,下划线开头后忽略变量,编译时不会发生警告
    let y = 6; // 不加且未使用则会发生警告
    let s = Some(String::from("d"));
    if let Some(_s) = s { println!("move s to _s") } //打印move s to _s
    println!("s={:?}", s); //再使用s则报错,因为已失去所有权

    //改使用_匹配则不会,可正常运行
    if let Some(_) = s { println!("not move s to _s") }
    println!("s={:?}", s);
}
  • 使用..忽略值的剩余部分
struct P { x: i32, y: i32, z: i32, }
fn main() {
    let p1 = P {x: 1, y: 2, z: 3};
    match p1 {//匹配第一个
        P {x, ..} => println!("{}", x),
    }
    let num = (2, 3, 4, 5, 6);
    match num {
        (f, .., l) => { // 匹配第一个和最后一个
            println!("{},{}", f, l)
        }
    }
    /*
    1
    2,6
    */
    match num {
        (.., f, ..) => { // 这种写法则会发生歧义,编译报错,编译器不知道取中间第几个
            println!("{}", f)
        }
    }
}

使用 match 守卫来提供额外的条件

match 守卫就是 match arm 模式后额外的if条件,想要匹配该条件也必须满足。
match 守卫适用于比单独的模式更复杂的场景。

fn main() {
    let b = Some(3);
    match b {
        Some(x) if x < 5 => { //匹配要求x小于5
            println!("x={} < 5", x)
        }
        Some(x) =>  println!("x={}", x),
        None => (),
    }
}
// x=3 < 5

另一个例子

fn main() {
    let a = Some(10);
    let b = 10;
    match a {
        Some(r) if r == b => { //if a == b不是模式,不会引入新的变量
            println!("r={} a==b", r)
        }
        Some(20) =>  println!("50"),
        _ => println!("a={:?}", a),
    }
}
// r=10 a==b

多重匹配例子:

fn main() {
    let a = 5;
    let b = true;
    match a { //使用多重模式,且b为true
        3 | 5 | 7 if b =>  println!("yes"), // 输出yes
        _ => println!("no"),
    }
}

@绑定

@符号使得可创建一个变量,该变量可在测试某个值是否与模式匹配的同时保存该值。

#[derive(Debug)]
enum M { H { i: i32 }, }
fn main() {
    let a = M::H { i: 5 };
    match a {
        M::H { // 匹配后,并将i赋给i_v变量
            i: i_v @ 3..=7,
        } =>  println!("i_v={}", i_v),
        // M::H { i: 10..=15 } => println!("i={:?} 10~15", i), //试图直接使用i,报错cannot find value `i` in this scope,所以要有上一行的@
        M::H { i: 10..=15 } => println!("a={:?} 10~15", a),
        M::H { i } => println!("i={}", i),
    }
}
// 输出i_v=5