match 表达式
Syntax
MatchExpression →
match Scrutinee {
InnerAttribute*
MatchArms?
}
Scrutinee → Expressionexcept StructExpression
MatchArms →
( MatchArm => ( ExpressionWithoutBlock , | ExpressionWithBlock ,? ) )*
MatchArm => Expression ,?
MatchArm → OuterAttribute* Pattern MatchArmGuard?
MatchArmGuard → if MatchConditions
MatchConditions →
MatchGuardChain
| Expression
MatchGuardChain → MatchGuardCondition ( && MatchGuardCondition )*
MatchGuardCondition →
Expressionexcept ExcludedMatchConditions
| OuterAttribute* let Pattern = MatchGuardScrutinee
MatchGuardScrutinee → Expressionexcept ExcludedMatchConditions
ExcludedMatchConditions →
LazyBooleanExpression
| RangeExpr
| RangeFromExpr
| RangeInclusiveExpr
| AssignmentExpression
| CompoundAssignmentExpression
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!(),
}
}
每个 | 分隔模式中的每个绑定都必须出现在该分支的所有模式中。
每个同名的绑定必须具有相同的类型和相同的绑定模式。
整个 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,每个操作数从左到右求值,直到某个操作数求值为 false 或 let 匹配失败,在这种情况下后续操作数不会被求值。
每个 let 模式的绑定被放入作用域,以供下一个条件操作数和匹配分支体使用。
如果任何守卫条件操作数是 let 模式,则由于与 let 受检者的歧义和优先级,所有条件操作数都不能是 || 惰性布尔运算符表达式。
Example
如果需要
||表达式,可以使用括号。例如:#![allow(unused)] fn main() { let foo = Some([123]); match foo { // 此处需要括号。 Some(xs) if let [x] = xs && (x < -100 || x > 20) => {} _ => {} } }