十六、Rust patterns

158 阅读5分钟

十六、Rust patterns

  • patternsRust 中特有的语法,用于匹配类型中的结构
  • patterns 组成:
    • 字面量 (字面值)
    • 解构的数组、枚举、结构体、元组
    • 变量
    • 通配符
    • 占位符

1. 使用位置

  • match 分支

  • if..let 表达式

  • while..let 条件循环

  • for..in 循环

    // 来自官方教程的一个示例
    let v = vec![1, 2, 3];
    
    for (index, value) in v.iter().enumerate() {
        println!("{} is at index {}", value, index);
    }
    
  • let 语句

    // 官方解释:let 语句更为正式的样子如下
    let PATTERN = EXPRESSION; // 变量模式
    
    // 元组解构模式,可以视为多个 `变量模式` 的组合
    let (x, y, z) = (1, 2, 3);
    
  • 函数参数

    • 在函数中的参数签名,也是一种模式
    • 类似的,在闭包中的签名,也是一种模式

2. 可反驳性

  • 可反驳性:Refutability
  • 模式有两种形式:refutable(可反驳的)和 irrefutable(不可反驳的)
  • 不可反驳的
    • 能匹配任何传递的可能值的模式
    • 比如:函数参数、 let 语句和 for 循环
      • 这三个只能接受不可反驳的模式
  • 可反驳的
    • 对某些值进行匹配时有可能匹配失败的模式
    • 比如:if..let, match, while..let 等条件语句
    • 只允许接受可反驳的模式:
      • if letwhile let 表达式
  • 特殊的模式:match 分支
    • 匹配分支必须使用可反驳模式
    • 最后一个分支必须使用能匹配任何剩余值的不可反驳模式
    • 可以在只有一个匹配分支的 match 中使用不可反驳模式,但这没有意义

3. 模式语法

(注:以下的代码均来自官方教程)

3.1 匹配字面值

  • 字面值,在 Javascript 中叫字面量
let x = 1;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"),
}

3.2 匹配命名变量

  • 命名变量是匹配任何值的不可反驳模式

    fn main() {
        let x = Some(5);
        let y = 10;
    
        match x {
            Some(50) => println!("Got 50"),
            // match 会创建一个新的作用域,因此这里的 y 和外面的 y 是不同的
            // Some(var) 中的 var 会匹配任何 Some 中的值,并将这个值赋值给自己
            Some(y) => println!("Matched, y = {:?}", y),
            _ => println!("Default case, x = {:?}", x),
        }
    
        println!("at the end: x = {:?}, y = {:?}", x, y);
    }
    

3.3 匹配多个模式

  • match 表达式中,可以使用 | 语法匹配多个模式

    let x = 1;
    
    match x {
        1 | 2 => println!("one or two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
    
    

3.4 匹配值的范围

  • ..= 语法允许你匹配一个闭区间范围内的值

    • 只允许用于数字或 char
    let x = 5;
    
    match x {
        1..=5 => println!("one through five"),
        _ => println!("something else"),
    }
    

3.5 解构

  • 解构元组

    fn main() {
        let (x, y, z) = (1, 2, 3);
        println!("x = {}, y = {}, z = {}", x, y, z);
    }
    
  • 解构结构体

    struct Point {
        x: i32,
        y: i32,
    }
    
    fn main() {
        let p = Point { x: 0, y: 7 };
    
        let Point { x, y: b } = p;
        assert_eq!(0, x);
        assert_eq!(7, b);
    }
    
  • 解构枚举

    enum Message {
        Quit,
        Write(String),
        Move { x: i32, y: i32 },
        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.")
            }
            Message::Write(text) => {
                println!("Text message: {}", text);
            }
            Message::Move { x, y } => {
                println!("x direction {}, y direction {}", x, y);
            }
            Message::ChangeColor(r, g, b) => {
                println!("red {}, green {}, blue {}", r, g, b);
            }
        }
    }
    
    
  • 嵌套的解构

    enum Color {
       Rgb(i32, i32, i32),
       Hsv(i32, i32, i32),
    }
    
    enum Message {
        Quit,
        Write(String),
        Move { x: i32, y: i32 },
        ChangeColor(Color),
    }
    
    fn main() {
        let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
    
        match msg {
            Message::ChangeColor(Color::Rgb(r, g, b)) => {
                println!("red {}, green {}, blue {}", r, g, b)
            }
            Message::ChangeColor(Color::Hsv(h, s, v)) => {
                println!("hue {}, saturation {}, value {}", h, s, v)
            }
            _ => ()
        }
    }
    
  • 一个复杂的解构

    let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
    

3.6 忽略模式值

  • 使用 _ 忽略整个值

    
    fn main() {
        let x = 5;
        match x {
            0 => {
                println!("x is zero");
            }
            _ => {
                println!("x is not zero");
            }
        }
    
        foo(1, 2);
    }
    
    fn foo(_: i32, b: i32) {
        println!("The second param, b, is {}", b);
    }
    
    
  • 使用 _ 忽略部分值

    let mut setting_value = Some(5);
    let new_setting_value = Some(10);
    
    match (setting_value, new_setting_value) {
        // 当不需要 Some 中的值时,在模式内使用下划线来匹配 Some 成员
        (Some(_), Some(_)) => {
            println!("Can't overwrite an existing customized value");
        }
        _ => {
            setting_value = new_setting_value;
        }
    }
    
    println!("setting is {:?}", setting_value);
    
  • 忽略未使用的变量,包括函数参数

    • 在名字前以一个下划线开头

      fn main() {
          let _x = 5;
          let y = 10;
          
          foo(10, 20);
      }
      
      fn foo(_a: i32, b: i32) {
          // 参数列表 (_a: i32, b: i32) 和 (_: i32, b: i32) 的区别:
          //      第一个参数列表,会将传入的值绑定到 _a 上;
          //      第二个参数列表,不会发生值绑定
          println!("param b is {}.", b);
      }
      
  • .. 忽略剩余值

    • 结构体、元组都可以使用。但是在元组中,一次结构时只能用一次这个特性

      struct Point {
          x: i32,
          y: i32,
          z: i32,
      }
      
      fn main() {
          // 解构元组
          let tuple = (1, 2, 3);
          let (_first, ..) = tuple;
          // let (.., second, ..) = tuple; // 不允许
          let (.., _third) = tuple;
          let (_1st, .., _3rd) = tuple;
          
          // 解构结构体
          let origin = Point { x: 0, y: 0, z: 0 };
          match origin {
              Point { x, .. } => println!("x is {}", x),
          }
      }
      
      

3.7 匹配守卫 提供额外条件

  • 匹配守卫:match guard

    • 指定于 match 分支模式之后的额外 if 条件,只有这个 if 条件被满足时,才能选择这个分支
  • 示例

    let num = Some(4);
    
    match num {
        Some(x) if x < 5 => println!("less than five: {}", x),
        Some(x) => println!("{}", x),
        None => (),
    }
    
  • 解决变量覆盖的问题

    fn main() {
        let x = Some(5);
        let y = 10;
    
        match x {
            Some(50) => println!("Got 50"),
            Some(n) if n == y => println!("Matched, n = {}", n),
            _ => println!("Default case, x = {:?}", x),
        }
    
        println!("at the end: x = {:?}, y = {}", x, y);
    }
    
    
  • 或运算符与匹配守卫

    fn main(){
        let x = 4;
        let y = false;
    
        match x {
            // 或运算符 `|` 优先级高于匹配守卫
            4 | 5 | 6 if y => println!("yes"),
            _ => println!("no"),
        }
    }
    

3.8 @ 绑定

  • 允许我们在创建一个存放值的变量的同时,测试其值是否匹配模式

    enum Message {
        Hello { id: i32 },
    }
    
    fn main() {
    
        let msg = Message::Hello { id: 5 };
    
        match msg {
            // 判断 id 是否在 3~7 这个范围,并将 id 重命名为 id_variable
            Message::Hello { id: id_variable @ 3..=7 } => {
                println!("Found an id in range: {}", id_variable)
            },
            // 判断 id 是否在 10~12 这个范围
            Message::Hello { id: 10..=12 } => {
                println!("Found an id in another range")
            },
            Message::Hello { id } => {
                println!("Found some other id: {}", id)
            },
        }
    }