模式适用场景

模式

模式是 Rust 中的特殊语法,它用来匹配类型中的结构和数据,它往往和 match 表达式联用,以实现强大的模式匹配能力。模式一般由以下内容组合而成:

  • 字面值
  • 解构的数组、枚举、结构体或者元组
  • 变量
  • 通配符
  • 占位符

所有可能用到模式的地方

match 分支

#![allow(unused)]
fn main() {
match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}
}

如上所示,match 的每个分支就是一个模式,因为 match 匹配是穷尽式的,因此我们往往需要一个特殊的模式 _,来匹配剩余的所有情况:

#![allow(unused)]
fn main() {
match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    _ => EXPRESSION,
}
}

if let 分支

if let 往往用于匹配一个模式,而忽略剩下的所有模式的场景:

#![allow(unused)]
fn main() {
if let PATTERN = SOME_VALUE {

}
}

while let 条件循环

一个与 if let 类似的结构是 while let 条件循环,它允许只要模式匹配就一直进行 while 循环。下面展示了一个使用 while let 的例子:

#![allow(unused)]
fn main() {
// Vec是动态数组
let mut stack = Vec::new();

// 向数组尾部插入元素
stack.push(1);
stack.push(2);
stack.push(3);

// stack.pop从数组尾部弹出元素
while let Some(top) = stack.pop() {
    println!("{}", top);
}
}

这个例子会打印出 32 接着是 1pop 方法取出动态数组的最后一个元素并返回 Some(value),如果动态数组是空的,将返回 None,对于 while 来说,只要 pop 返回 Some 就会一直不停的循环。一旦其返回 Nonewhile 循环停止。我们可以使用 while let 来弹出栈中的每一个元素。

你也可以用 loop + if let 或者 match 来实现这个功能,但是会更加啰嗦。

for 循环

#![allow(unused)]
fn main() {
let v = vec!['a', 'b', 'c'];

for (index, value) in v.iter().enumerate() {
    println!("{} is at index {}", value, index);
}
}

这里使用 enumerate 方法产生一个迭代器,该迭代器每次迭代会返回一个 (索引,值) 形式的元组,然后用 (index,value) 来匹配。

let 语句

#![allow(unused)]
fn main() {
let PATTERN = EXPRESSION;
}

是的, 该语句我们已经用了无数次了,它也是一种模式匹配:

#![allow(unused)]
fn main() {
let x = 5;
}

这其中,x 也是一种模式绑定,代表将匹配的值绑定到变量 x 上。因此,在 Rust 中,变量名也是一种模式,只不过它比较朴素很不起眼罢了。

#![allow(unused)]
fn main() {
let (x, y, z) = (1, 2, 3);
}

上面将一个元组与模式进行匹配(模式和值的类型必需相同!),然后把 1, 2, 3 分别绑定到 x, y, z 上。

模式匹配要求两边的类型必须相同,否则就会导致下面的报错:

#![allow(unused)]
fn main() {
let (x, y) = (1, 2, 3);
}
#![allow(unused)]
fn main() {
error[E0308]: mismatched types
 --> src/main.rs:4:5
  |
4 | let (x, y) = (1, 2, 3);
  |     ^^^^^^   --------- this expression has type `({integer}, {integer}, {integer})`
  |     |
  |     expected a tuple with 3 elements, found one with 2 elements
  |
  = note: expected tuple `({integer}, {integer}, {integer})`
             found tuple `(_, _)`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error
}

对于元组来说,元素个数也是类型的一部分!

函数参数

函数参数也是模式:

#![allow(unused)]
fn main() {
fn foo(x: i32) {
    // 代码
}
}

其中 x 就是一个模式,你还可以在参数中匹配元组:

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}

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

&(3, 5) 会匹配模式 &(x, y),因此 x 得到了 3y 得到了 5

let 和 if let

对于以下代码,编译器会报错:

#![allow(unused)]
fn main() {
let Some(x) = some_option_value;
}

因为右边的值可能不为 Some,而是 None,这种时候就不能进行匹配,也就是上面的代码遗漏了 None 的匹配。

类似 let , formatch 都必须要求完全覆盖匹配,才能通过编译( 不可驳模式匹配 )。

但是对于 if let,就可以这样使用:

#![allow(unused)]
fn main() {
if let Some(x) = some_option_value {
    println!("{}", x);
}
}

因为 if let 允许匹配一种模式,而忽略其余的模式( 可驳模式匹配 )。

let-else(Rust 1.65 新增)

使用 let-else 匹配,即可使 let 变为可驳模式。它可以使用 else 分支来处理模式不匹配的情况,但是 else 分支中必须用发散的代码块处理(例如:breakreturnpanic)。请看下面的代码:

use std::str::FromStr;

fn get_count_item(s: &str) -> (u64, &str) {
    let mut it = s.split(' ');
    let (Some(count_str), Some(item)) = (it.next(), it.next()) else {
        panic!("Can't segment count item pair: '{s}'");
    };
    let Ok(count) = u64::from_str(count_str) else {
        panic!("Can't parse integer: '{count_str}'");
    };
    // error: `else` clause of `let...else` does not diverge
    // let Ok(count) = u64::from_str(count_str) else { 0 };
    (count, item)
}

fn main() {
    assert_eq!(get_count_item("3 chairs"), (3, "chairs"));
}

matchif let 相比,let-else 的一个显著特点在于其解包成功时所创建的变量具有更广的作用域。在 let-else 语句中,成功匹配后的变量不再仅限于特定分支内使用:

#![allow(unused)]
fn main() {
// if let
if let Some(x) = some_option_value {
    println!("{}", x);
}

// let-else
let Some(x) = some_option_value else { return; }
println!("{}", x);
}

在上面的例子中,if let 写法里的 x 只能在 if 分支内使用,而 let-else 写法里的 x 则可以在 let 之外使用。