模式
语法
模式 :
|
? 非顶层可选模式 (|
非顶层可选模式 )*非顶层可选模式 :
无范围模式
| 范围模式无范围模式 :
字面量模式
| 标识符模式
| 通配符模式
| 剩余模式
| 引用模式
| 结构体模式
| 元组结构体模式
| 元组模式
| 分组模式
| 切片模式
| 路径模式
| 宏调用
模式用于将值与结构进行匹配,并可选地将变量绑定到这些结构内的值。它们还用于变量声明以及函数和闭包的参数。
以下示例中的模式执行四项操作
- 测试
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); } }
字面量模式
语法
字面量模式 :
true
|false
| 字符字面量
| 字节字面量
| 字符串字面量
| 原始字符串字面量
| 字节字符串字面量
| 原始字节字符串字面量
| C 字符串字面量
| 原始 C 字符串字面量
|-
? 整数字面量
|-
? 浮点数字面量
字面量模式 与字面量创建的值完全匹配。由于负数不是 字面量,因此字面量模式也接受字面量前的可选减号,其作用类似于否定运算符。
目前接受浮点数字面量,但由于比较它们的复杂性,在未来版本的 Rust 中,它们将在字面量模式中被禁止(参见 问题 #41620)。
字面量模式接受 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"), } } }
标识符模式
标识符模式将它们匹配的值绑定到一个变量。标识符在模式中必须是唯一的。该变量将隐藏作用域中任何同名的变量。新绑定的作用域取决于使用模式的上下文(例如 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) => (), } }
在第一个匹配表达式中,值被复制(或移动)。在第二个匹配中,对相同内存位置的引用被绑定到变量值。需要这种语法,因为在解构子模式时,&
运算符不能应用于值的字段。例如,以下代码无效
#![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
并且标识符遮蔽了常量,则会出现错误。
如果 @
子模式是不可反驳的或未指定子模式,则标识符模式是不可反驳的。
绑定模式
为了提供更好的 ergonomics,模式在不同的*绑定模式*下运行,以便更容易地将引用绑定到值。当引用值由非引用模式匹配时,它将被自动视为 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
。如果自动解引用的值仍然是引用,则对其进行解引用,并重复此过程。
移动绑定和引用绑定可以在同一个模式中混合使用。这样做会导致对象的部分移动被绑定,并且该对象之后不能再使用。这仅适用于无法复制的类型。
在下面的示例中,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; }
通配符模式
语法
通配符模式 :
_
通配符模式(下划线符号)匹配任何值。当值无关紧要时,它用于忽略值。在其他模式中,它匹配单个数据字段(与匹配剩余字段的 ..
相反)。与标识符模式不同,它不会复制、移动或借用它匹配的值。
示例
#![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 {} }
通配符模式始终是不可反驳的。
剩余模式
语法
剩余模式 :
..
剩余模式(..
标记)充当可变长度模式,它匹配之前和之后尚未匹配的零个或多个元素。它只能在 元组、元组结构体 和 切片 模式中使用,并且只能作为这些模式中的一个元素出现一次。它也允许在仅用于 切片模式 的 标识符模式 中使用。
剩余模式始终是不可反驳的。
示例
#![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"), } }
范围模式
语法
范围模式 :
范围包含模式
| 范围起始模式
| 范围结束包含模式
| 已弃用范围模式范围包含模式 :
范围模式边界..=
范围模式边界范围起始模式 :
范围模式边界..
范围结束包含模式 :
..=
范围模式边界已弃用范围模式 :
范围模式边界...
范围模式边界
范围模式 匹配其边界定义的范围内的标量值。它们包含一个*符号*(..
、..=
或 ...
之一)和一侧或两侧的边界。符号左侧的边界是*下界*。右侧的边界是*上界*。
同时具有下界和上界的范围模式将匹配其上下界之间和包括其上下界的所有值。它的写法是下界,后跟 ..=
,再后跟上界。范围模式的类型是其上下界的类型统一。
例如,模式 'm'..='p'
将仅匹配值 'm'
、'n'
、'o'
和 'p'
。
下界不能大于上界。也就是说,在 a..=b
中,必须满足 a ≤ b。例如,范围模式 10..=0
是错误的。
只有下界的范围模式将匹配大于或等于下界的任何值。它的写法是下界后跟 ..
,并且与其下界具有相同的类型。例如,1..
将匹配 1、9 或 9001,或 9007199254740991(如果它的大小合适),但不匹配 0,也不匹配有符号整数的负数。
只有上界的范围模式匹配小于或等于上界的任何值。它的写法是 ..=
后跟上界,并且与其上界具有相同的类型。例如,..=10
将匹配 10、1、0,以及有符号整数类型的所有负值。
只有一个边界的范围模式不能用作 切片模式 中子模式的顶层模式。
边界写成以下之一
- 字符、字节、整数或浮点数字面量。
-
后跟整数或浮点数字面量。- 路径
如果边界写成路径,则在宏解析后,路径必须解析为类型为 char
、整数类型或浮点类型的常量项。
边界类型和值取决于其编写方式。 如果边界是一个 路径,则模式具有路径解析到的 常量 的类型和值。 如果它是一个字面量,则它具有相应的 字面量表达式 的类型和值。 如果它是一个前面带有 -
的字面量,则它具有与相应的 字面量表达式 相同的类型,并且其值为 取反 相应字面量表达式的值。
示例
#![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..=6 => "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", }); }
当固定宽度整数和 char
类型的范围模式跨越类型的整个可能值集时,它们是不可反驳的。 例如,0u8..=255u8
是不可反驳的。 整数类型的取值范围是从其最小值到最大值的闭区间。 char
类型的取值范围恰好是包含所有 Unicode 标量值的范围:'\u{0000}'..='\u{D7FF}'
和 '\u{E000}'..='\u{10FFFF}'
。
不推荐使用浮点范围模式,并且可能会在未来的 Rust 版本中删除。 有关更多信息,请参阅 问题 #41620。
版本差异:在 2021 版之前,同时具有下限和上限的范围模式也可以使用
...
代替..=
来编写,含义相同。
注意:尽管范围模式使用与 范围表达式 相同的语法,但没有排他性范围模式。 也就是说,
x .. y
和.. x
都不是有效的范围模式。
引用模式
语法
ReferencePattern :
(&
|&&
)mut
? PatternWithoutRange
引用模式解引用正在匹配的指针,因此借用了它们。
例如,x: &i32
上的这两个匹配是等效的
#![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); }
引用模式的语法产生式必须匹配标记 &&
以匹配对引用的引用,因为它本身就是一个标记,而不是两个 &
标记。
添加 mut
关键字会解引用可变引用。 可变性必须与引用的可变性相匹配。
引用模式始终是不可反驳的。
结构体模式
语法
StructPattern :
PathInExpression{
StructPatternElements ?
}
StructPatternElements :
StructPatternFields (,
|,
StructPatternEtCetera)?
| StructPatternEtCeteraStructPatternFields :
StructPatternField (,
StructPatternField) *StructPatternField :
OuterAttribute *
(
TUPLE_INDEX:
Pattern
| IDENTIFIER:
Pattern
|ref
?mut
? IDENTIFIER
)StructPatternEtCetera :
OuterAttribute *
..
结构体模式匹配与其子模式定义的所有条件相匹配的结构体、枚举和联合值。 它们还用于 解构 结构体、枚举或联合值。
在结构体模式上,字段通过名称、索引(在元组结构体的情况下)引用,或者通过使用 ..
忽略
#![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 {..} => (), } }
如果未使用 ..
,则用于匹配结构体的结构体模式需要指定所有字段
#![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: _} => (), } }
用于匹配联合的结构体模式必须精确指定一个字段(请参阅 对联合进行模式匹配)。
ref
和/或 mut
IDENTIFIER 语法匹配任何值,并将其绑定到与给定字段同名的变量。
#![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 }
如果 PathInExpression 解析为具有多个变体的枚举的构造函数,或者其子模式之一是可反驳的,则结构体模式是可反驳的。
元组结构体模式
语法
TupleStructPattern :
PathInExpression(
TupleStructItems?)
元组结构体模式匹配与其子模式定义的所有条件相匹配的元组结构体和枚举值。 它们还用于 解构 元组结构体或枚举值。
如果 PathInExpression 解析为具有多个变体的枚举的构造函数,或者其子模式之一是可反驳的,则元组结构体模式是可反驳的。
元组模式
语法
TuplePattern :
(
TuplePatternItems?)
TuplePatternItems :
Pattern,
| 剩余模式
| Pattern (,
Pattern)+,
?
元组模式匹配与其子模式定义的所有条件相匹配的元组值。 它们还用于 解构 元组。
具有单个 RestPattern 的 (..)
形式是一种特殊形式,不需要逗号,并且可以匹配任何大小的元组。
当其子模式之一是可反驳的时候,元组模式是可反驳的。
使用元组模式的示例
#![allow(unused)] fn main() { let pair = (10, "ten"); let (a, b) = pair; assert_eq!(a, 10); assert_eq!(b, "ten"); }
分组模式
语法
GroupedPattern :
(
Pattern)
将模式括在括号中可用于显式控制复合模式的优先级。 例如,范围模式旁边的引用模式(例如 &0..=5
)是不明确的,因此是不允许的,但可以使用括号来表示。
#![allow(unused)] fn main() { let int_reference = &3; match int_reference { &(0..=5) => (), _ => (), } }
切片模式
语法
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 */ } }; }
只要每个元素都是不可反驳的,切片模式在匹配数组时就是不可反驳的。 当匹配切片时,只有在具有单个 ..
剩余模式 或以 ..
剩余模式作为子模式的 标识符模式 的形式下,它才是不可反驳的。
在切片中,没有下限和上限的范围模式必须用括号括起来,例如 (a..)
,以表明它旨在匹配单个切片元素。 具有下限和上限的范围模式(例如 a..=b
)不需要用括号括起来。
路径模式
语法
PathPattern :
PathExpression
路径模式是指代常量值或没有字段的结构体或枚举变体的模式。
非限定路径模式可以引用
- 枚举变体
- 结构体
- 常量
- 关联常量
限定路径模式只能引用关联常量。
常量不能是联合类型。 结构体和枚举常量必须具有 #[derive(PartialEq, Eq)]
(而不仅仅是实现)。
当路径模式引用结构体或枚举变体(当枚举只有一个变体时)或类型不可反驳的常量时,它们是不可反驳的。 当它们引用可反驳的常量或具有多个变体的枚举的枚举变体时,它们是可反驳的。
或模式
或模式是匹配两个或多个子模式之一的模式(例如 A | B | C
)。 它们可以任意嵌套。 在语法上,或模式允许在允许其他模式的任何地方使用(由 Pattern 产生式表示),但 let
绑定以及函数和闭包参数除外(由 PatternNoTopAlt 产生式表示)。
静态语义
-
给定某个深度处的模式
p | q
,对于某些任意模式p
和q
,如果满足以下条件,则该模式被认为是格式错误的- 为
p
推断的类型与为q
推断的类型不统一,或者 - 在
p
和q
中没有引入相同的绑定集,或者 p
和q
中具有相同名称的任何两个绑定的类型在类型或绑定模式方面不统一。
在所有上述情况下,类型的统一都是精确的,并且不应用隐式 类型强制转换。
- 为
-
当类型检查表达式
match e_s { a_1 => e_1, ... a_n => e_n }
时,对于每个包含形式为p_i | q_i
的模式的匹配臂a_i
,如果在存在片段e_s
的深度d
处,表达式片段的类型与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
的嵌套模式。请注意,这里所说的“构造函数”不是指元组结构体模式,而是指任何乘积类型的模式。这包括枚举变体、元组结构体、具有命名字段的结构体、数组、元组和切片。
动态语义
- 在深度
d
处,将被匹配表达式e_s
与模式c(p | q, ..rest)
进行模式匹配的动态语义定义为与c(p, ..rest) | c(q, ..rest)
相同,其中c
是某个构造函数,p
和q
是任意模式,rest
是c
中可选的任何剩余潜在因子。
与其他无界模式的优先级
如本章其他地方所示,有几种类型的模式在语法上是无界的,包括标识符模式、引用模式和或模式。或模式始终具有最低优先级。这允许我们为未来可能的类型标注功能保留语法空间,并减少歧义。例如,x @ A(..) | B(..)
将导致错误,因为 x
未在所有模式中绑定。&A(x) | B(x)
将导致不同子模式中 x
的类型不匹配。