Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

match 表达式

match 表达式基于模式进行分支。所发生的匹配的确切形式取决于模式

match 表达式有一个*受检者表达式*,即要与模式进行比较的值。

受检者表达式和模式必须具有相同的类型。

match 的行为取决于受检者表达式是位置表达式还是值表达式

如果受检者表达式是值表达式,它首先被求值到一个临时位置,然后结果值按顺序与分支中的模式进行比较,直到找到匹配。第一个具有匹配模式的分支被选为 match 的分支目标,模式绑定的任何变量被赋值给分支块中的局部变量,控制进入该块。

当受检者表达式是位置表达式时,match 不分配临时位置;然而,按值绑定可能会从内存位置复制或移动。如果可能,最好对位置表达式进行匹配,因为这类匹配的生命周期继承位置表达式的生命周期,而不是被限制在 match 内部。

match 表达式示例:

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

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    4 => println!("four"),
    5 => println!("five"),
    _ => println!("something else"),
}
}

模式内绑定的变量的作用域是匹配守卫和分支表达式。

绑定模式(移动、复制或引用)取决于模式。

可以使用 | 运算符连接多个匹配模式。每个模式将按从左到右的顺序进行测试,直到找到成功的匹配。

#![allow(unused)]
fn main() {
let x = 9;
let message = match x {
    0 | 1  => "not many",
    2 ..= 9 => "a few",
    _      => "lots"
};

assert_eq!(message, "a few");

// 模式匹配顺序的演示。
struct S(i32, i32);

match S(1, 2) {
    S(z @ 1, _) | S(_, z @ 2) => assert_eq!(z, 1),
    _ => panic!(),
}
}

Note

2..=9 是一个区间模式,而不是区间表达式。因此,只有区间模式支持的那些类型的区间才能用于匹配分支中。

每个 | 分隔模式中的每个绑定都必须出现在该分支的所有模式中。

每个同名的绑定必须具有相同的类型和相同的绑定模式。

整个 match 表达式的类型是各个匹配分支的最小上界

如果没有匹配分支,则 match 表达式是发散的,类型为 !

Example

#![allow(unused)]
fn main() {
fn make<T>() -> T { loop {} }
enum Empty {}

fn diverging_match_no_arms() -> ! {
    let e: Empty = make();
    match e {}
}
}

如果受检者表达式或所有匹配分支发散,则整个 match 表达式也发散。

匹配守卫

匹配分支可以接受匹配守卫以进一步细化匹配某个情况的条件。

模式守卫出现在 if 关键字之后的模式之后,由一个具有布尔类型表达式或一个条件 let 匹配组成。

当模式成功匹配时,模式守卫被执行。如果所有守卫条件操作数都求值为 true 且所有 let 模式都成功匹配其受检者,则该匹配分支被成功匹配,并执行分支体。

否则,测试下一个模式,包括同一分支中使用 | 运算符的其他匹配。

#![allow(unused)]
fn main() {
let maybe_digit = Some(0);
fn process_digit(i: i32) { }
fn process_other(i: i32) { }
let message = match maybe_digit {
    Some(x) if x < 10 => process_digit(x),
    Some(x) => process_other(x),
    None => panic!(),
};
}

Note

使用 | 运算符的多重匹配可能导致模式守卫及其副作用被多次执行。例如:

#![allow(unused)]
fn main() {
use std::cell::Cell;
let i : Cell<i32> = Cell::new(0);
match 1 {
    1 | _ if { i.set(i.get() + 1); false } => {}
    _ => {}
}
assert_eq!(i.get(), 2);
}

模式守卫可以引用在其后模式内绑定的变量。

在求值守卫之前,会对受检者中变量匹配的部分获取共享引用。在求值守卫期间,访问变量时使用此共享引用。

只有当守卫成功求值时,值才从受检者移动或复制到变量中。这允许在守卫内部使用共享借用,而如果在守卫未匹配的情况下不必从受检者中移出。

此外,通过在求值守卫时持有共享引用,也防止了守卫内部的修改。

守卫可以使用 let 模式来有条件地匹配受检者,并在模式成功匹配时将新变量绑定到作用域中。

Example

在此示例中,守卫条件 let Some(first_char) = name.chars().next() 被求值。如果 let 模式成功匹配(即字符串至少有一个字符),则执行分支体。否则,模式匹配继续到下一个分支。

let 模式创建一个新绑定(first_char),它可以在分支体中与原始模式绑定(name)一起使用。

#![allow(unused)]
fn main() {
enum Command {
    Run(String),
    Stop,
}
let cmd = Command::Run("example".to_string());

match cmd {
    Command::Run(name) if let Some(first_char) = name.chars().next() => {
        // 此处 `name` 和 `first_char` 都可用
        println!("Running: {name} (starts with '{first_char}')");
    }
    Command::Run(name) => {
        println!("{name} is empty");
    }
    _ => {}
}
}

匹配守卫链

多个守卫条件操作数可以用 && 分隔。

Example

#![allow(unused)]
fn main() {
let foo = Some([123]);
let already_checked = false;
match foo {
    Some(xs) if let [single] = xs && !already_checked => { dbg!(single); }
    _ => {}
}
}

类似于 && LazyBooleanExpression,每个操作数从左到右求值,直到某个操作数求值为 falselet 匹配失败,在这种情况下后续操作数不会被求值。

每个 let 模式的绑定被放入作用域,以供下一个条件操作数和匹配分支体使用。

如果任何守卫条件操作数是 let 模式,则由于与 let 受检者的歧义和优先级,所有条件操作数都不能是 || 惰性布尔运算符表达式

Example

如果需要 || 表达式,可以使用括号。例如:

#![allow(unused)]
fn main() {
let foo = Some([123]);
match foo {
    // 此处需要括号。
    Some(xs) if let [x] = xs && (x < -100 || x > 20) => {}
    _ => {}
}
}

匹配分支上的属性

匹配分支上允许外部属性。在匹配分支上有意义的唯一属性是 cfglint 检查属性

在匹配表达式开花括号后直接允许内部属性,其所在表达式上下文与块表达式上的属性相同。