模式
语法
Pattern :
|
? PatternNoTopAlt (|
PatternNoTopAlt )*PatternNoTopAlt :
PatternWithoutRange
| RangePatternPatternWithoutRange :
PatternWithoutRange
| IdentifierPattern
| WildcardPattern
| RestPattern
| ReferencePattern
| StructPattern
| TupleStructPattern
| TuplePattern
| GroupedPattern
| SlicePattern
| PathPattern
| MacroInvocation
模式用于将值与结构匹配,并可选择地将变量绑定到这些结构内部的值。它们也用于变量声明以及函数和闭包的参数。
以下示例中的模式做了四件事
- 测试
person
是否具有填充了值的car
字段。 - 测试 person 的
age
字段是否在 13 到 19 之间,并将其值绑定到person_age
变量。 - 将
name
字段的引用绑定到变量person_name
。 - 忽略
person
的其余字段。剩余字段可以具有任何值,并且不绑定到任何变量。
#![allow(unused)] fn main() { struct Car; struct Computer; struct Person { name: String, car: Option<Car>, computer: Option<Computer>, age: u8, } let person = Person { name: String::from("John"), car: Some(Car), computer: None, age: 15, }; if let Person { car: Some(_), age: person_age @ 13..=19, name: ref person_name, .. } = person { println!("{} has a car and is {} years old.", person_name, person_age); } }
模式用于
解构
模式可用于解构 结构体、枚举 和 元组。解构将值分解为其组成部分。使用的语法几乎与创建这些值时相同。
在模式中,如果其检查对象表达式具有 struct
、enum
或 tuple
类型,则占位符 (_
) 代表单个数据字段,而通配符 ..
代表特定变体的所有剩余字段。
解构具有命名(但非编号)字段的数据结构时,允许将 fieldname
写成 fieldname: fieldname
的缩写形式。
#![allow(unused)] fn main() { enum Message { Quit, WriteString(String), Move { x: i32, y: i32 }, ChangeColor(u8, u8, u8), } let message = Message::Quit; match message { Message::Quit => println!("Quit"), Message::WriteString(write) => println!("{}", &write), Message::Move{ x, y: 0 } => println!("move {} horizontally", x), Message::Move{ .. } => println!("other move"), Message::ChangeColor { 0: red, 1: green, 2: _ } => { println!("color change, red: {}, green: {}", red, green); } }; }
可反驳性
当模式可能与被匹配的值不匹配时,称该模式是可反驳的。另一方面,不可反驳的模式总是与被匹配的值匹配。示例
#![allow(unused)] fn main() { let (x, y) = (1, 2); // "(x, y)" is an irrefutable pattern if let (a, 3) = (1, 2) { // "(a, 3)" is refutable, and will not match panic!("Shouldn't reach here"); } else if let (a, 4) = (3, 4) { // "(a, 4)" is refutable, and will match println!("Matched ({}, 4)", a); } }
字面量模式
语法
PatternWithoutRange :
true
|false
| CHAR_LITERAL
| BYTE_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
| C_STRING_LITERAL
| RAW_C_STRING_LITERAL
|-
? INTEGER_LITERAL
|-
? FLOAT_LITERAL
字面量模式完全匹配由字面量创建的值。由于负数不是字面量,字面量模式也接受在字面量之前可选的负号,其作用类似于否定运算符。
警告
字面量模式中接受 C 字符串和原生 C 字符串字面量,但
&CStr
未实现结构性相等(#[derive(Eq, PartialEq)]
),因此对&CStr
的任何此类match
都将被拒绝并报类型错误。
字面量模式总是可反驳的。
示例
#![allow(unused)] fn main() { for i in -2..5 { match i { -1 => println!("It's minus one"), 1 => println!("It's a one"), 2|4 => println!("It's either a two or a four"), _ => println!("Matched none of the arms"), } } }
标识符模式
语法
IdentifierPattern :
ref
?mut
? IDENTIFIER (@
PatternNoTopAlt ) ?
标识符模式将其匹配的值绑定到值命名空间中的变量。
标识符在模式中必须是唯一的。
新绑定变量将遮蔽作用域中同名的变量。新绑定的作用域取决于模式的使用上下文(例如 let
绑定或 match
分支)。
仅由标识符组成的模式(可能带 mut
)匹配任何值并将其绑定到该标识符。这是变量声明以及函数和闭包参数中最常用的模式。
#![allow(unused)] fn main() { let mut variable = 10; fn sum(x: i32, y: i32) -> i32 { x + y } }
要将模式的匹配值绑定到变量,请使用语法 variable @ subpattern
。例如,以下内容将值 2 绑定到 e
(而不是整个范围:这里的范围是范围子模式)。
#![allow(unused)] fn main() { let x = 2; match x { e @ 1 ..= 5 => println!("got a range element {}", e), _ => println!("anything"), } }
默认情况下,标识符模式将变量绑定到匹配值的副本或从匹配值移动,具体取决于匹配值是否实现了 Copy
特性。
可以通过使用 ref
关键字将其更改为绑定到引用,或使用 ref mut
绑定到可变引用。例如
#![allow(unused)] fn main() { let a = Some(10); match a { None => (), Some(value) => (), } match a { None => (), Some(ref value) => (), } }
在第一个 match 表达式中,值被复制(或移动)。在第二个 match 中,对同一内存位置的引用被绑定到变量 value。需要此语法是因为在解构子模式中,&
运算符不能应用于值字段。例如,以下内容无效
#![allow(unused)] fn main() { struct Person { name: String, age: u8, } let value = Person { name: String::from("John"), age: 23 }; if let Person { name: &person_name, age: 18..=150 } = value { } }
为了使其有效,请编写以下内容
#![allow(unused)] fn main() { struct Person { name: String, age: u8, } let value = Person { name: String::from("John"), age: 23 }; if let Person { name: ref person_name, age: 18..=150 } = value { } }
因此,ref
不是用来匹配的。它的目的完全是为了使匹配的绑定成为引用,而不是潜在地复制或移动匹配的值。
路径模式优先于标识符模式。
如果指定了 ref
或 ref mut
并且标识符遮蔽了一个常量,则会出错。
如果 @
子模式不可反驳,或者未指定子模式,则标识符模式是不可反驳的。
绑定模式
为了提供更好的用户体验,模式以不同的绑定模式运行,以便更容易将引用绑定到值。当引用值被非引用模式匹配时,它将自动被视为 ref
或 ref mut
绑定。示例
#![allow(unused)] fn main() { let x: &Option<i32> = &Some(3); if let Some(y) = x { // y was converted to `ref y` and its type is &i32 } }
非引用模式包括所有模式,除了绑定模式、通配符模式 (_
)、引用类型的 const
模式 和 引用模式。
如果绑定模式未显式指定 ref
、ref mut
或 mut
,则它使用默认绑定模式来确定变量如何绑定。
默认绑定模式从“移动”模式开始,该模式使用移动语义。
匹配模式时,编译器从模式的外部开始向内部工作。
每当引用使用非引用模式匹配时,它将自动解引用该值并更新默认绑定模式。
引用将默认绑定模式设置为 ref
。
可变引用将模式设置为 ref mut
,除非模式已经是 ref
,在这种情况下它保持 ref
。
如果自动解引用的值仍然是引用,则它会被解引用,并且此过程重复进行。
仅当默认绑定模式为“移动”时,绑定模式才可以显式指定 ref
或 ref mut
绑定模式,或使用 mut
指定可变性。例如,以下内容不被接受
#![allow(unused)] fn main() { let [mut x] = &[()]; //~ ERROR let [ref x] = &[()]; //~ ERROR let [ref mut x] = &mut [()]; //~ ERROR }
版本差异:在 2024 版本之前,即使默认绑定模式不是“移动”,绑定也可以显式指定
ref
或ref mut
绑定模式,并且可以在此类绑定上使用mut
指定可变性。在这些版本中,在绑定上指定mut
会将绑定模式设置为“移动”,无论当前的默认绑定模式如何。
类似地,引用模式只能在默认绑定模式为“移动”时出现。例如,这不被接受
#![allow(unused)] fn main() { let [&x] = &[&()]; //~ ERROR }
版本差异:在 2024 版本之前,即使默认绑定模式不是“移动”,引用模式也可以出现,并且既具有与被匹配值匹配的效果,又导致默认绑定模式重置为“移动”。
移动绑定和引用绑定可以混合在同一个模式中。这样做会导致对象的局部移动,之后该对象无法再使用。这仅适用于类型不可复制(Copy
)的情况。
在下面的示例中,name
从 person
中移出。尝试将 person
作为一个整体或 person.name
使用会导致错误,因为发生了局部移动。
示例
#![allow(unused)] fn main() { struct Person { name: String, age: u8, } let person = Person{ name: String::from("John"), age: 23 }; // `name` is moved from person and `age` referenced let Person { name, ref age } = person; }
通配符模式
语法
WildcardPattern :
_
通配符模式(下划线符号 _
)匹配任何值。当值不重要时,它用于忽略值。
在其他模式内部,它匹配单个数据字段(与匹配剩余字段的 ..
不同)。
与标识符模式不同,它不复制、移动或借用它匹配的值。
示例
#![allow(unused)] fn main() { let x = 20; let (a, _) = (10, x); // the x is always matched by _ assert_eq!(a, 10); // ignore a function/closure param let real_part = |a: f64, _: f64| { a }; // ignore a field from a struct struct RGBA { r: f32, g: f32, b: f32, a: f32, } let color = RGBA{r: 0.4, g: 0.1, b: 0.9, a: 0.5}; let RGBA{r: red, g: green, b: blue, a: _} = color; assert_eq!(color.r, red); assert_eq!(color.g, green); assert_eq!(color.b, blue); // accept any Some, with any value let x = Some(10); if let Some(_) = x {} }
通配符模式总是不可反驳的。
剩余模式
语法
RestPattern :
..
剩余模式(..
标记)充当可变长度模式,匹配之前和之后尚未匹配的零个或多个元素。
它只能用于元组、元组结构体和切片模式,并且在这些模式中只能作为其中一个元素出现一次。它也仅在切片模式的标识符模式中被允许使用。
剩余模式总是不可反驳的。
示例
#![allow(unused)] fn main() { let words = vec!["a", "b", "c"]; let slice = &words[..]; match slice { [] => println!("slice is empty"), [one] => println!("single element {}", one), [head, tail @ ..] => println!("head={} tail={:?}", head, tail), } match slice { // Ignore everything but the last element, which must be "!". [.., "!"] => println!("!!!"), // `start` is a slice of everything except the last element, which must be "z". [start @ .., "z"] => println!("starts with: {:?}", start), // `end` is a slice of everything but the first element, which must be "a". ["a", end @ ..] => println!("ends with: {:?}", end), // 'whole' is the entire slice and `last` is the final element whole @ [.., last] => println!("the last element of {:?} is {}", whole, last), rest => println!("{:?}", rest), } if let [.., penultimate, _] = slice { println!("next to last is {}", penultimate); } let tuple = (1, 2, 3, 4, 5); // Rest patterns may also be used in tuple and tuple struct patterns. match tuple { (1, .., y, z) => println!("y={} z={}", y, z), (.., 5) => println!("tail must be 5"), (..) => println!("matches everything else"), } }
范围模式
语法
RangePattern :
RangeExclusivePattern
| RangeInclusivePattern
| RangeFromPattern
| RangeToExclusivePattern
| RangeToInclusivePattern
| ObsoleteRangePattern1RangeExclusivePattern :
RangeExclusivePatternRangePatternBound
..
RangePatternBound :
RangeInclusivePatternRangePatternBound
..=
RangePatternBound :
RangeFromPatternRangePatternBound
..
:
RangeToExclusivePattern
..
RangePatternBound :
RangeToInclusivePattern
..=
RangePatternBound :
ObsoleteRangePatternRangePatternBound
...
RangePatternBound :
RangePatternBound
| BYTE_LITERAL
|-
? INTEGER_LITERAL
|-
? FLOAT_LITERAL
CHAR_LITERAL1
[patterns.range.intro]
范围模式匹配在其边界定义的范围内的标量值。它们包含一个标记(..
或 ..=
)以及一侧或两侧的边界。
[patterns.range.exclusive]
排他范围模式匹配从下界开始到上界(但不包括上界)的所有值。它被写为下界,后跟 ..
,再后跟上界。
[patterns.range.inclusive]
包含范围模式匹配从下界开始到上界(包括上界)的所有值。它被写为下界,后跟 ..=
,再后跟上界。
[patterns.range.from]
起始范围模式匹配所有大于或等于下界的值。它被写为下界后跟 ..
。
[patterns.range.to-exclusive]
终止排他范围模式匹配所有小于上界的值。它被写为 ..
后跟上界。
[patterns.range.to-inclusive]
终止包含范围模式匹配所有小于或等于上界的值。它被写为 ..=
后跟上界。
[patterns.range.constraint-less-than]
[patterns.range.bound]
- 边界可以写成以下形式之一
- 字符、字节、整数或浮点字面量。
-
后跟整数或浮点字面量。
[patterns.range.constraint-bound-path]
[patterns.range.type]
[patterns.range.path-value]
[patterns.range.literal-value]
[patterns.range.negation]
[patterns.range.float-restriction]
示例
#![allow(unused)] fn main() { let c = 'f'; let valid_variable = match c { 'a'..='z' => true, 'A'..='Z' => true, 'α'..='ω' => true, _ => false, }; let ph = 10; println!("{}", match ph { 0..7 => "acid", 7 => "neutral", 8..=14 => "base", _ => unreachable!(), }); let uint: u32 = 5; match uint { 0 => "zero!", 1.. => "positive number!", }; // using paths to constants: const TROPOSPHERE_MIN : u8 = 6; const TROPOSPHERE_MAX : u8 = 20; const STRATOSPHERE_MIN : u8 = TROPOSPHERE_MAX + 1; const STRATOSPHERE_MAX : u8 = 50; const MESOSPHERE_MIN : u8 = STRATOSPHERE_MAX + 1; const MESOSPHERE_MAX : u8 = 85; let altitude = 70; println!("{}", match altitude { TROPOSPHERE_MIN..=TROPOSPHERE_MAX => "troposphere", STRATOSPHERE_MIN..=STRATOSPHERE_MAX => "stratosphere", MESOSPHERE_MIN..=MESOSPHERE_MAX => "mesosphere", _ => "outer space, maybe", }); pub mod binary { pub const MEGA : u64 = 1024*1024; pub const GIGA : u64 = 1024*1024*1024; } let n_items = 20_832_425; let bytes_per_item = 12; if let size @ binary::MEGA..=binary::GIGA = n_items * bytes_per_item { println!("It fits and occupies {} bytes", size); } trait MaxValue { const MAX: u64; } impl MaxValue for u8 { const MAX: u64 = (1 << 8) - 1; } impl MaxValue for u16 { const MAX: u64 = (1 << 16) - 1; } impl MaxValue for u32 { const MAX: u64 = (1 << 32) - 1; } // using qualified paths: println!("{}", match 0xfacade { 0 ..= <u8 as MaxValue>::MAX => "fits in a u8", 0 ..= <u16 as MaxValue>::MAX => "fits in a u16", 0 ..= <u32 as MaxValue>::MAX => "fits in a u32", _ => "too big", }); }
[patterns.range.refutable]
[patterns.range.refutable-integer]
[patterns.range.refutable-char]
[patterns.range.constraint-slice]
[patterns.range.edition2021]
[patterns.ref]
语法
[patterns.ref.syntax] :
ReferencePattern
[patterns.ref.intro]
引用模式解引用被匹配的指针,从而借用它们。
#![allow(unused)] fn main() { let int_reference = &3; let a = match *int_reference { 0 => "zero", _ => "some" }; let b = match int_reference { &0 => "zero", _ => "some" }; assert_eq!(a, b); }
[patterns.ref.ref-ref]
[patterns.ref.mut]
[patterns.ref.refutable]
[patterns.struct]
语法
[patterns.struct.syntax] :
StructPattern
PathInExpression{
}
StructPatternElements ? :
}
StructPatternElementsStructPatternFields (
,
|,
StructPatternEtCetera)? :
| StructPatternEtCeteraStructPatternFields :
StructPatternField (,
StructPatternField) *
StructPatternField
OuterAttribute *
(
TUPLE_INDEX:
Pattern
| IDENTIFIER:
Pattern|
ref
?mut
? IDENTIFIER :
..
StructPatternEtCetera
[patterns.struct.intro]
#![allow(unused)] fn main() { struct Point { x: u32, y: u32, } let s = Point {x: 1, y: 1}; match s { Point {x: 10, y: 20} => (), Point {y: 10, x: 20} => (), // order doesn't matter Point {x: 10, ..} => (), Point {..} => (), } struct PointTuple ( u32, u32, ); let t = PointTuple(1, 2); match t { PointTuple {0: 10, 1: 20} => (), PointTuple {1: 10, 0: 20} => (), // order doesn't matter PointTuple {0: 10, ..} => (), PointTuple {..} => (), } enum Message { Quit, Move { x: i32, y: i32 }, } let m = Message::Quit; match m { Message::Quit => (), Message::Move {x: 10, y: 20} => (), Message::Move {..} => (), } }
[patterns.struct.ignore-rest]
#![allow(unused)] fn main() { struct Struct { a: i32, b: char, c: bool, } let mut struct_value = Struct{a: 10, b: 'X', c: false}; match struct_value { Struct{a: 10, b: 'X', c: false} => (), Struct{a: 10, b: 'X', ref c} => (), Struct{a: 10, b: 'X', ref mut c} => (), Struct{a: 10, b: 'X', c: _} => (), Struct{a: _, b: _, c: _} => (), } }
[patterns.struct.constraint-struct]
[patterns.struct.constraint-union]
#![allow(unused)] fn main() { struct Struct { a: i32, b: char, c: bool, } let struct_value = Struct{a: 10, b: 'X', c: false}; let Struct{a: x, b: y, c: z} = struct_value; // destructure all fields }
[patterns.struct.binding-shorthand]
[patterns.struct.refutable]
语法
[patterns.tuple-struct] :
元组结构体模式[patterns.tuple-struct.syntax] :
TupleStructPattern
TupleStructItems
[patterns.tuple-struct.intro]
[patterns.tuple-struct.refutable]
语法
[patterns.tuple] :
元组模式[patterns.tuple.syntax] :
TuplePattern
| RestPattern(
TuplePatternItems?)
Pattern ,
[patterns.tuple.intro]
[patterns.tuple.rest-syntax]
形式为 (..)
的单个RestPattern 是一种特殊形式,不需要逗号,并且匹配任意大小的元组。
#![allow(unused)] fn main() { let pair = (10, "ten"); let (a, b) = pair; assert_eq!(a, 10); assert_eq!(b, "ten"); }
当其某个子模式是可反驳的时,元组模式是可反驳的。
语法
[patterns.paren] :
分组模式
GroupedPattern
#![allow(unused)] fn main() { let int_reference = &3; match int_reference { &(0..=5) => (), _ => (), } }
[patterns.paren.intro]
语法
[patterns.slice] :
切片模式[patterns.slice.syntax] :
SlicePattern
SlicePatternItems
#![allow(unused)] fn main() { // Fixed size let arr = [1, 2, 3]; match arr { [1, _, _] => "starts with one", [a, b, c] => "starts with something else", }; }
#![allow(unused)] fn main() { // Dynamic size let v = vec![1, 2, 3]; match v[..] { [a, b] => { /* this arm will not apply because the length doesn't match */ } [a, b, c] => { /* this arm will apply */ } _ => { /* this wildcard is required, since the length is not known statically */ } }; }
[patterns.slice.intro]
[patterns.slice.refutable-array]
[patterns.slice.refutable-slice]
[patterns.slice.restriction]
语法
[patterns.path] :
路径模式
PathPattern
[patterns.path.intro]
- 路径模式是指向常量值或没有字段的结构体或枚举变体的模式。
- [patterns.path.unqualified]
- 不限定的路径模式可以引用
- 枚举变体
常量
[patterns.path.qualified]
[patterns.path.refutable]
[patterns.const]
[patterns.const.partial-eq]
- [patterns.const.structural-equality]
- [patterns.const.primitive]
- [patterns.const.builtin-aggregate]
- [patterns.const.ref]
- [patterns.const.aggregate]
- [patterns.const.pointer]
- [patterns.const.float]
[patterns.const.exhaustive]
[patterns.const.generic]
[patterns.const.translation]
在确保满足所有条件后,常量值被转换为模式,并且行为与直接编写该模式完全一致。特别是,它完全参与穷尽性检查。(对于原始指针,常量是编写此类模式的唯一方式。对于这些类型,只有 _
被视为穷尽的。)
或模式
-
[patterns.constraints]
- 静态语义
- [patterns.constraints.pattern]
- 给定任意模式
p
和q
在某个深度的模式p | q
,如果出现以下情况,则认为该模式格式不正确
推断出
p
的类型与推断出q
的类型不统一,或
- 在
p
和q
中,具有相同名称的任意两个绑定在类型或绑定模式方面不统一。
-
[patterns.constraints.match-type-check]
当类型检查表达式
match e_s { a_1 => e_1, ... a_n => e_n }
时,对于每个包含形式为p_i | q_i
的模式的 match 分支a_i
,如果其存在的深度d
处的e_s
片段类型与p_i | q_i
不统一,则认为模式p_i | q_i
格式不正确。
关于穷尽性检查,模式 p | q
被认为覆盖 p
和 q
。对于某个构造函数 c(x, ..)
,分配律适用,使得 c(p | q, ..rest)
覆盖与 c(p, ..rest) | c(q, ..rest)
相同的值集。这可以递归应用,直到除了顶层模式外没有其他形式为 p | q
的嵌套模式。
- [patterns.behavior]
[patterns.behavior.nested-or-patterns]
将检查对象表达式 e_s
与在深度 d
处的模式 c(p | q, ..rest)
进行模式匹配的动态语义定义为与 c(p, ..rest) | c(q, ..rest)
相同,其中 c
是某个构造函数,p
和 q
是任意模式,rest
可选地是 c
中任何剩余的潜在因子。