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 表达式的示例:
#![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"),
}
}
模式内绑定的变量在 match 守卫和分支表达式的作用域内。
绑定模式(移动、复制或引用)取决于模式。
多个匹配模式可以用 | 运算符连接。将按从左到右的顺序测试每个模式,直到找到成功的匹配。
#![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");
// Demonstration of pattern match order.
struct S(i32, i32);
match S(1, 2) {
S(z @ 1, _) | S(_, z @ 2) => assert_eq!(z, 1),
_ => panic!(),
}
}
每个 | 分隔的模式中的每个绑定必须出现在分支中的所有模式中。
同名的每个绑定必须具有相同的类型和相同的绑定模式。
整个 match 表达式的类型是各个 match 分支的最小上界。
如果没有 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 分支都发散,则整个 match 表达式也发散。
Match 守卫
Match 分支可以接受 _match 守卫_以进一步细化匹配情况的标准。
模式守卫出现在 if 关键字后面的模式之后,由具有布尔类型的 Expression 或条件 let 匹配组成。
当模式成功匹配时,执行模式守卫。如果所有守卫条件操作数都求值为 true 并且所有 let 模式都成功匹配其审查者,则 match 分支被成功匹配,执行分支主体。
否则,测试下一个模式,包括同一分支中使用 | 运算符的其他匹配。
#![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() => { // Both `name` and `first_char` are available here println!("Running: {name} (starts with '{first_char}')"); } Command::Run(name) => { println!("{name} is empty"); } _ => {} } }
Match 守卫链
多个守卫条件操作数可以用 && 分隔。
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); } _ => {} } }
类似于 && 惰性布尔表达式,每个操作数从左到右求值,直到操作数求值为 false 或 let 匹配失败,在这种情况下不求值后续操作数。
每个 let 模式的绑定被放入作用域,以供下一个条件操作数和 match 分支主体使用。
如果任何守卫条件操作数是 let 模式,则由于 let 审查者的歧义和优先级,任何条件操作数都不能是 || 惰性布尔运算符表达式。
Example
如果需要
||表达式,则可以使用括号。例如:#![allow(unused)] fn main() { let foo = Some([123]); match foo { Some(xs) if let [x] = xs // Parentheses are required here. && (x < -100 || x > 20) => {} _ => {} } }