rust 模式匹配 pattern match

Created

2024-05-27 16:23:24

Updated

2024-10-27 14:10:26

1 概念逻辑

Important
  • 模式匹配是一种编程语言特性, 允许你将值(简单的值或者复杂的数据结构)与预定义的模式进行比较,并在匹配成功时执行相应的代码(变量绑定或其他操作)
  • 结合下图, 然后后面的实际例子, 体会一下
  • 就我个人印象, 了解下模式匹配的定义(没错,就是定义) 是很有好处的, 之前就直接开干,概念没有特别去思考, 有些东西会有些模糊
Diagram

2 let

let PATTERN = EXPRESSION;

fn main() {
    // 这种变量申明初始化, 其实就是模式匹配
    // x 就是pattern , 然后1 就是 x 要与之匹配的值
    // 这个就属于 irrefutable 类型的模式, 因为x 能匹配上任何东西, 必定匹配上的.
    let x = 1;

    // (b,c) 是模式, 右边的(1,2) 是值, 结构上能够匹配上
    // 所以 将 1 绑定到变量b, 将 2 绑定到变量c
    let (b, c) = (1, 2);
    println!("{}-{}", b, c);
    // 忽略第一个元素 , 这里 _ 就是图一里提到的占位符模式
    let (_, y) = (1, 2);
    println!("y: {}", y);

    struct Person {
        name: String,
        age: i32,
    }
    let p1 = Person {
        name: "zhang fei".to_string(),
        age: 22,
    };
    // 同理
    let Person { name: n, age: m } = p1;
    println!("{} : {}", n, m);

    let p2 = Person {
        name: "liu bei".to_string(),
        age: 32,
    };
    // 名字与字段名一样的情况,可以省略
    let Person { name, age } = p2;
    println!("{} : {}", name, age);
    let p3 = Person {
        name: "guan yu".to_string(),
        age: 13,
    };
    // 可以使用.. 省略
    let Person { name,.. } = p3;
    println!("{}",name);
}
支持& 这种匹配
fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}

fn main() {
    let point = (3, 5);
    // 函数传递参数 相当于 let 形参=传递的实参
    // let &(x, y)= &(3,5)
    print_coordinates(&point);

    // 理解了 这也是模式匹配, 下面这个就不觉得奇怪了
    let &a = &11;
    println!("{}", a);
}

3 match

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

3.1 枚举

模式是枚举

fn main() {
    let x = Some(5);
    // 这里的 x 是值
    match x {
        // Some(value) 是模式
        Some(value) => {
            // 匹配成功后,执行相应代码
            // Some(value)=Some(5) 这里也绑定了 变量, value=5
            println!("Got a value: {}", value)
        }
        // None 是模式
        None => println!("No value"),
    }
}
enum Coin {
    Penny,
    Nickel,
    Dime(String),
    Quarter,
}

fn main() {
    let c = Coin::Penny;
    let d = Coin::Dime("hello".to_string());
    // 这里 let r=  就是前面我们提到 irrefutable 类型的模式
    // 然后右边本身也含着一个 模式匹配
    let r = match d {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime(s) => {
            println!("{}", s); // 可以在这边获取枚举成员携带的数据
            10 //返回值
        }
        // 必须把所有成员匹配都写上,否则报错, 去看 Pattern Syntax
        Coin::Quarter => 25,
    };
    println!("{}", r);
}

3.2 字面量

fn main(){
    let x = 1;

    match x {
        // 这里的数字1  就是模式, 是字面量的模式
        1 => println!("一"),
        2 => println!("二"),
        // 虽然这个也匹配, 但是只会执行前面第一个匹配的分支
        1 => println!("1"),
        3 => println!("三"),
        4 => println!("四"),
        5 => println!("五"),
        // _ 是占位符模式
        _ => println!("其他数字"),
    }
    match x{
        2=>println!("二"),
        // 可以随便一个变量名来表示其他数据
        other=>println!("其他数字: {}",other),
    }
}

3.3 结构体

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 10, y: 0 };

    match p {
        Point { x, y: 0 } => println!("y=0 才能匹配上 {}", x),
        Point { x, y: 0..=2 } => println!("y>=0 <=2 是才匹配上 {}", x),
        Point { x: 0, y } => println!("x=0 才能匹配上 {}", y),
        Point { x, y } => println!("都能匹配上 ({}, {})", x, y),
    }
}

4 if let

相当于 使用 match ,然后只匹配一个成员,然后执行代码块, 更加简洁

fn main() {
    // Some 这个是Option<t> 枚举里的成员, 因为 默认 use了,所以可以直接拿来用
    let config_max = Some(3u8);
    match config_max {
        Some(max) => println!("The maximum is configured to be {}", max),
        _ => (),
    }
    let config_max = Some(3u8);
    // 相当于上面的 match, 但是我们这里不用写 另外 _ 的分支
    // 模式匹配上,绑定值到变量max, 然后执行 {} 里的代码, 匹配不上 就继续后面的代码
    // 和if 判断 很像
    if let Some(max) = config_max {
        println!("The maximum is configured to be {}", max);
    }
}
fn main() {
    let n = 1;
    // 1=n , 1 是字面量 模式(pattern)
    // n 是值, 这里匹配上了
    if let 1 = n {
        println!("{}", 1);
    } else {  // 也能 else if {} else{}
        println!("{}", 2);
    }
    // 类似我们之前if 条件语句那边
    let r = if let 1 = n { 11 } else { 22 };
    println!("{}", r);
}
fn main() {
    // 之前 这种 let x =1  很显然也可以 使用 if
    // 只是这样写, 会提示你 这个肯定会匹配上, 不需要写 if 这种
    if let x = 1 {
        println!("hello")
    }
    //错误, 提示在这个作用域中找不到x
    // println!("{}", x);
}

5 while let

fn main() {
    let mut optional = Some(0);

    // let Some(i)=optional 匹配上, 就进入{}
    // 1. 第一次 匹配上 i=0
    while let Some(i) = optional {
        if i > 9 {
            println!("Greater than 9, quit!");
            optional = None;
        } else {
            // 2. 到这里, ... 到 i=9 最后一次在这里,然后 i>9,走上面的分支
            println!("`i` is `{:?}`. Try again.", i);
            optional = Some(i + 1);
        }
    }
}

6 let ref

ref 是模式的一部分, 而 & 是借用运算符,是表达式的一部分

fn main() {
    let ref_c2 = &1;
    // 等价上面,  如果是ref mut 就是可变借用了
    let ref ref_c1 = 1;
    println!("{}-{}", *ref_c1, *ref_c2);
}
看一下这个例子, 报错了
struct Person {
    name: String,
    age: i32,
}
fn main() {
    let p1 = Person {
        name: "zhang fei".to_string(),
        age: 22,
    };
    let Person { name, age } = p1;
    println!("{} : {}", name, age);
    // 我们发现这里会报错,提示 p1.name 已经被move给前面的 name了
    // 因为相当于变量赋值,name 是 move 类别
    println!("{}-{}", p1.age, p1.name);
}
我们使用ref 借用name就行
struct Person {
    name: String,
    age: i32,
}
fn main() {
    let p1 = Person {
        name: "zhang fei".to_string(),
        age: 22,
    };
    let Person { ref name, age } = p1;
    println!("{} : {}", name, age);
    let Person {
        // 这里的name 是字段, n 才是我们要的值, 所以别弄错, ref是修饰n
        name: ref n,
        age: m,
    } = p1;
    println!("{} : {}", n, m);
}
报错: 枚举中发生move
fn main() {
    let x: Option<String> = Some("hello".to_string());
    match x {
        // 注意是在这里 发生了 move
        // Some(e)=x, e 获取了x 里部分数据的所有权.
        Some(e) => println!("{}", e),
        _ => println!("other"),
    }
    // x中的数据被转移所有权了,所以这里报错了
    println!("{:?}", x);
}
添加ref
fn main() {
    let x: Option<String> = Some("hello".to_string());
    match x {
        // 加上ref ,表示这里e 是 借用了 x里面的数据
        // x 数据是完整的, 后面就不会报错了.
        Some(ref e) => println!("hello{}", e),
        _ => println!("other"),
    }

    println!("{:?}", x);
}
使用match &x
fn main() {
    let x: Option<String> = Some("hello".to_string());
    // match &x
    match &x {
        // 编译器自动将e弄成 &String ,对 x 内 字符串的借用了
        // 如果是 match &mut x 同理
        Some(e) => println!("hello{}", *e),
        _ => println!("other"),
    }

    println!("{:?}", x);
}
struct Person {
    name: String,
    age: i32,
}
fn main() {
    let mut p1 = Person {
        name: "zhang fei".to_string(),
        age: 22,
    };
    let Person { ref mut name, age } = p1;
    println!("{} : {}", name, age);
    name.push_str(" hello");
    println!("{}", p1.name);
}

7 let mut

Caution

没错, 我们平常使用的let mut x=1 类似这种语句, mut 是模式匹配中模式的一部分

struct Person {
    name: String,
    age: i32,
}
fn main() {
    let mut x=2;
    let (mut a, mut b) = (1, 2);
    a = 33;
    println!("{}", a);
    struct Person {
        name: String,
        age: i32,
    }
    let p1 = Person {
        name: "zhang fei".to_string(),
        age: 22,
    };
    let Person { name, age } = p1;
    println!("{} : {}", name, age);
    // name.push_str("string"); 报错,因为name 不可变
    let p1 = Person {
        name: "zhang fei".to_string(),
        age: 22,
    };
    let Person { mut name, age } = p1;
    name.push_str("string"); // ok
}

8 Pattern Syntax

8.1 .. 剩余

struct Point {
    x: i32,
    y: i32,
    z: i32,
}
fn main() {
    let (a, ..) = (1, 2, 3, 4, 5);
    println!("{}", a); // 1
    let (b, .., c) = (1, 2, 3, 4, 5);
    println!("{}-{}", b, c); // 1-5

    // 结构体 不能 将.. 放中间
    let Point { x: x1, .. } = Point {
        x: 11,
        y: 22,
        z: 33,
    };
    println!("{}", x1); // 1
}

8.2 _ 忽略绑定

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

    let y = Some(String::from("hello"));
    // - 没有绑定, 所以没有move,后面的y打印不会报错
    if let Some(_) = y {
        println!("ss");
    };
    println!("{:?}", y);
}

8.3 _ 剩余部分

成员不用全部写上的方式

enum Coin {
    Penny,
    Nickel,
    Dime(String),
    Quarter,
}

fn main() {
    let d = Coin::Dime("hello".to_string());
    let r = match d {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        // 用other 表示其他情况
        other => 666,
    };
    println!("{}", r);
}

使用_来表示不匹配的情况,这里的和枚举显然不一样,在枚举里如果要写,是肯定能匹配上的,这里是完全可能匹配不上,所以用 _ 来区分 前面用的other.

其实都用 _ 就行

fn main() {
    let d = 3;
    let r = match d {
        1 => 11,
        2 => 22,
        _ => 888,
    };
    println!("{}", r);
}

8.4 | 和 if

fn main() {
    let x = 4;
    let y = true;

    match x {
        // 或者等4或5,或6 并且 y为真才行, y是外部定义的那个y
        4 | 5 | 6 if y => println!("yes"),
        7 | 8 => println!("7-8"),
        _ => println!("no"),
    }
}
fn main() {
    let x = Some(5);
    let y = 5;

    match x {
        Some(50) => println!("50"),
        // ==y 的y 是外部 定义的 y =5
        // Some(y) 的y 与外部无关
        Some(y) if y == y => {
            println!("Matched, n = {}", y)
        }
        _ => println!("Default case, x = {:?}", x),
    }
}

8.5 ..= 范围

>=xx <=yy

fn main() {
    let x = 5i32;

    match x {
        1..=5 => println!(">=1 <=5"),
        _ => println!("something else"),
    }
    println!("i32最小值{}", i32::MIN);
    match x {
        i32::MIN..=2_i32 => println!("<=2"),
        3 => println!("=3"),
        4 => println!("=4"),
        5_i32..=i32::MAX => println!(">=5"),
    };
}

8.6 @ 绑定

enum Student {
    primary { age: i32 },
    junior { age: i32 },
}
fn main() {
    let s = Student::primary { age: 3 };

    match s {
        Student::primary { age: primary_age } if primary_age >= 7 && primary_age <= 11 => {
            println!("Hello")
        }
        // 相当于上面 primary_age >= 3 && primary_age <= 6
        // @前面是绑定的变量 
        Student::primary {
            // 这里就表示当你符合 >=3 <=6 ,绑定匹配到的age到primary_age 这个变量
            age: primary_age @ 3..=6,
        } => println!("是 >=3 <=6 岁的小学生: {}", primary_age),
        Student::junior { age: 10..=12 } => {
            println!("10-12 岁的初中生")
        }
        Student::junior { age } => println!("Found: {}", age),
        _ => (),
    }

    let a=Some(42);
    match a {
        // 得到 `Some` 可变类型,如果它的值(绑定到 `n` 上)等于 42,则匹配。
        Some(n @ 42) => println!("The Answer: {}!", n),
        // 匹配任意其他数字。
        Some(n)      => println!("Not interesting... {}", n),
        // 匹配任意其他值(`None` 可变类型)。
        _            => (),
    }
}
fn main() {
    let x = 11;
    match x {
        // | 多个情况 绑定
        e @ 11 | e @ 1..=3 => println!("{}", e),
        _ => println!("other"),
    }
}

9 non_exhaustive

Caution
  1. 当你使用上游开发人员创建的一个enum, 然后你写了全部分支, OK.
  2. 突然有一天, 上游人员给这个enum 添加了一个变体成员, 这个时候因为你的模式匹配enum中没有写_ 这种,导致现在没有写完全部分支,错误发生了
  3. non_exhaustive就是为了防止这种情况的发生而设计的
.
├── Cargo.toml # package : world
└── src
    ├── lib.rs 
    ├── main.rs
pub mod enum_ex {
    // 这个属性, 表示 其他人 match时 必须使用 _ 来整完剩余分支
    #[non_exhaustive]
    pub enum Coin {
        Penny,
        Nickel,
        Dime(String),
        Quarter,
    }
}
use world::enum_ex::Coin;

fn main() {
    let c = Coin::Penny;
    let d = Coin::Dime("hello".to_string());
    let r = match d {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime(s) => {
            println!("{}", s);
            10
        }
        Coin::Quarter => 25,
        // 必须 使用 _, 否则报错,编译不过的
        _ ==> 0,
    };
    println!("{}", r);
}
Back to top