Rust 模式匹配详解

2022-03-29 rust language

在 Rust 中没有 switch 语句,但是提供了匹配器 matcher,相比其它语言中的 switch 要更强大。

基本概念

match 语句是 Rust 中的一种控制流语句,可以根据不同的模式匹配执行不同的代码,其基本语法如下:

match value {
    pattern1 => {
        // code1
    }
    pattern2 => {
        // code2
    }
    _ => {
        // not match
    }
}

其中 value 是要匹配的变量,pattern 是匹配模式,=> 后面是要执行的代码块,如果 value 匹配了某个模式,就会执行对应的代码块;如果 value 没有匹配任何模式,就会执行默认的代码块,也就是 _ => {...} 模块。

简单示例

这是 Rust 中比较常用的语法,用来匹配类型中的结构,无论类型是简单还是复杂。模式常见于如下场景:参数析构、match 等。

fn main() {
    let x = 1;
    match x {
        0 => println!("zero"),                // Simple patterns
        1 | 2 => println!("one or two"),      // Multiple patterns
        3 ..= 5 => println!("three to five"), // Ranges [3, 5]
        v @ 6 ..= 10 => println!("got a range element {}", v), // Bindings
        _ => println!("anything"),
    }
}

如上的最简单的示例,支持匹配单个值、多个值、范围等,允许绑定到某个变量然后直接引用。

枚举

enum Operation {
    Quit,
    Move {x: i32, y: i32},
    Message(String),
}

fn main() {
    let msg = Operation::Move { x: 1, y: 2 };
    match msg {
        Operation::Quit => return,
        Operation::Move { x, y }=> println!("POS=({}, {})", x, y),
        Operation::Message(s)=> println!("MSG=({})", s),
        _ => return,
    }
}

可以定义一个枚举类型,然后根据枚举中的不同类型匹配,允许从中提取变量。

最常用的还是标准的 Option 类型,示例如下。

fn main() {
    let y = Some(10);
    match y {
        Option::Some(i) if i > 5 => println!("got value {}", i), // Match guards
        Option::Some(..) => println!("some value"), // Ignoring the value and type
        Option::None => println!("none"),
    }
}

引用

fn main() {
    let z = 10;
    match z {
        ref r => {println!("A reference {}", r);} // Must surrounded by a body
    }

    let mut u = 10;
    match u {
        ref mut r => {println!("A mutable reference {}", r);} // Mutable
    }

    let r = &z;
    match r {
        &10 => println!("The value is ten"),
        _ => println!("The value is not ten"),
    }
}

允许匹配某个引用类型,甚至是具体的某个值。

高级用法

元组和结构体

fn main() {
	struct Point {
        x: i32,
        y: i32,
    }
    let p = Point {x: 10, y: 20};
    match p {
        Point {y: yy, .. } => {println!("y is {}", yy);}
    }

    let point = (1, 2);
    match point {
        (0, 0) => println!("The point is at the origin"),
        (_, 0) => println!("The point is on the x-axis"),
        (0, _) => println!("The point is on the y-axis"),
        (x, y) => println!("The point is at ({}, {})", x, y),
    }
}

上述的结构体示例中仅获取其中一个变量,还可以使用 Point { x: Some(ref name), y: None } => ... 这种方式,只是其中的 xSome 类型,然后从其中获取引用,并命名为 name 变量。

守卫

fn main() {
    let x = 5;
    match x {
        n if n < 0 => println!("The value is negative, {}", n),
        n if n > 10 => println!("The value is greater than 10, {}", n),
        _ => println!("The value is between 0 and 10"),
    }
}

除了匹配某个值还可以设置某个条件,当满足具体条件时才执行对应的分支。

绑定变量

fn main() {
    let x = Some(5);
    match x {
        Some(n @ 1..=10) => println!("The value is between 1 and 10: {}", n),
        Some(n @ 11..=20) => println!("The value is between 11 and 20: {}", n),
        Some(_) => println!("The value is not between 1 and 20"),
        None => (),
    }
}

也就是通过 @ 绑定变量,后面代码可以直接使用;或者通过 _ 忽略对应的值。

简化匹配

fn main() {
    let x = Some(5);
    if let Some(n) = x {
        println!("The value is {}", n);
    } else {
        println!("The value is None");
    }
}

使用的时候可能只需要查看是否满足其中一种条件,那么就可以简单通过 if let 这种语法糖的方式使用。

简化循环

fn main() {
    let v = vec![1, 2, 3];
    for n in &v {
        println!("{}", n);
    }

    let mut v = vec![1, 2, 3];
    while let Some(n) = v.pop() {
        println!("{}", n);
    }
}

可以通过 while let 简化循环的处理逻辑。

简化使用

除了上述的 match 方式,还可以使用 if let 的方式,区别是,后者不会检查是否关联了所有分支。

enum Direction {
    Up,
    Down,
    Left,
    Right,
}

let dir = Direction::Down;
if let Direction::Left = dir {
    println!("Left");
} else if let Direction::Right = dir {
    println!("Right");
} else {
    println!("Dummy");
}