模式
语法
模式 :
|
? 模式非顶层项 (|
模式非顶层项 )*模式非顶层项 :
无区间模式
| 区间模式无区间模式 :
字面值模式
| 标识符模式
| 通配符模式
| 剩余模式
| 引用模式
| 结构体模式
| 元组结构体模式
| 元组模式
| 分组模式
| 切片模式
| 路径模式
| 宏调用
模式用于将值与结构进行匹配,并在这些结构内将变量绑定到值(可选)。 它们还用于函数和闭包的变量声明及参数。以下示例中的模式执行四个操作:
- 检查
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); } }
模式用于:
解构
模式可以用于解构 structs 、 enums 和 tuples 。解构是将一个值分解成其组成部分。
使用的语法与创建这些值时几乎相同。在一个模式中,其 被匹配项 表达式可为 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)" 是一个不可拒绝的模式 if let (a, 3) = (1, 2) { // "(a, 3)" 是可拒绝的,将不匹配 panic!("不应该到达这里"); } else if let (a, 4) = (3, 4) { // "(a, 4)" 是可拒绝的,并将匹配,且作了值绑定 println!("匹配到 ({}, 4)", a); } }
字面值模式
语法
字面值模式 :
true
|false
| 字符字面值
| 字节字面值
| 字符串字面值
| 原始字符串字面值
| 字节字符串字面值
| 原始字节字符串字面值
|-
? 整数字面值
|-
? 浮点数字面值
字面值模式 匹配与所创建字面值相同的值。 虽然负数不是字面值,但字面值模式接受字面值前面的可选负号。
目前接受浮点字面值,但由于行为比较复杂,未来的 Rust 版本中将禁止在字面值模式中使用它们 (参见问题 #41620)。
字面值模式总是可拒绝的。
例如:
#![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) => (), } }
在第一个 match 表达式中,该值被复制 (或移动) 。在第二个 match 中,对同一内存位置的引用被绑定到变量值。
这种语法是必要的,因为在解构子模式中, &
操作符不能应用于值的字段。
例如,以下是无效的:
#![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); // 创建一个类型为 &Option<i32> 的引用 x,指向 Some(3) if let Some(y) = x { // 对 x 进行模式匹配 // y 被转换成了 `ref y`,其类型为 &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` 从 `person` 中被移动了,而 `age` 则被引用。 let Person { name, ref age } = person; }
通配符模式
语法
通配符模式 :
_
通配符模式 (下划线符号) 匹配任何值。其作用是忽略不重要的值。
在其他模式内部,它匹配单个数据字段 (与 ..
匹配多个剩余字段不同)。
与标识符模式不同,它不会复制、移动或借用它所匹配的值。
例如:
#![allow(unused)] fn main() { let x = 20; let (a, _) = (10, x); // x总是被 _ 匹配 assert_eq!(a, 10); // 忽略函数/闭包的参数 let real_part = |a: f64, _: f64| { a }; // 忽略函数/闭包的参数 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); // 接受任何 Some,与任何值 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 { // 忽略除了最后一个元素之外的所有元素,最后一个元素必须是 "!" [.., "!"] => println!("!!!"), // `start` 是除了最后一个元素之外的所有元素,最后一个元素必须是 "z" [start @ .., "z"] => println!("starts with: {:?}", start), // `end` 是除了第一个元素之外的所有元素,第一个元素必须是"a" ["a", end @ ..] => println!("ends with: {:?}", end), // 'whole' 是整个切片, `last` 是最后一个元素 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); // 剩余模式也可以用于元组和元组结构体模式。 match tuple { // 匹配第一个元素和其余元素,但是只保留最后两个元素 (1, .., y, z) => println!("y={} z={}", y, z), // 必须以 5 结尾 (.., 5) => println!("tail must be 5"), // 匹配所有其他情况 (..) => println!("matches everything else"), } }
区间模式
语法
区间模式 :
区间内部模式
| 区间From模式
| 区间To内部模式
| 废弃区间模式区间内部模式 :
区间模式约束..=
区间模式约束区间From模式 :
区间模式约束..
区间To内部模式 :
..=
区间模式约束废弃区间模式 :
区间模式约束...
区间模式约束
区间模式 匹配由其边界定义的区间内的标量值。它们由一个 符号 ( ..
、 ..=
或 ...
中的一个) 和一个或两个边界组成。在符号左边的边界是 下界 ,在右边的是 上界 。
具有下界和上界的区间模式将匹配其两个边界之间及其包括的所有值。它由其下界、后跟 ..=
,后跟其上界组成。区间模式的类型与其上界和下界的类型统一。
例如,模式 'm'..='p'
将仅匹配值 'm'
'n'
'o'
和 'p'
。
下界不能大于上界。也就是说,在 a..=b
中,必须满足 $a ≤ b$。例如,拥有区间模式 10..=0
是错误的。
只有下界的区间模式将匹配大于或等于下界的任何值。它由其下界后跟 ..
组成,与其下界具有相同的类型。例如, 1..
将匹配 1、9、9001 或 9007199254740991 (如果它是适当大小的) ,但不匹配 0 和有符号整数的负数。
只有上界的区间模式将匹配小于或等于上界的任何值。它由 ..=
后跟其上界组成,与其上界具有相同的类型。例如, ..=10
将匹配 10、1、0 和有符号整数类型的所有负值。
只有一个边界的区间模式不能用作 切片模式 的子模式的顶级模式。
边界值可以写作以下其中之一:
- 字符、字节、整数或浮点数字面值。
-
后跟一个整数或浮点数字面值。- 路径。
如果边界值为路径,在宏展开后,该路径必须解析为类型为 char
、整数类型或浮点类型的常量条目。
边界的类型和值取决于其书写方式。如果边界是一个路径,则模式具有路径解析后的常量的类型和值。
如果是字面值,则具有相应的字面值表达式的类型和值。
如果是以 -
开头的字面值,则具有与相应字面值表达式相同的类型,以及相应字面值表达式的值的 取反 后的值。
Examples:
#![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
类型的区间模式在它们 span 类型的所有可能值时是不可拒绝的。
例如, 0u8..=255u8
是不可拒绝的。
整数类型的值区间是从其最小值到最大值的闭区间。
char
类型的值区间恰好包含所有 Unicode 标量值的区间: '\U{0000}'..='\U{D7FF}'
和 '\U{E000}'..='\U{10FFFF}'
。
浮点数区间模式已被弃用,可能会在未来的Rust版本中删除。 有关更多信息,请参见 issue #41620 。
版本差异: 在 2021 版之前,带有下限和上限的区间模式也可以使用
...
代替..=
进行编写,具有相同的含义。
注意: 尽管区间模式使用与 区间表达式 相同的语法,但不存在独占区间模式。 也就是说,
x .. y
和.. x
都不是有效的区间模式。
引用模式
语法
引用模式 :
(&
|&&
)mut
? 无区间模式
引用模式会对匹配值的指针进行解引用,并借用。
例如,以下两个对 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
关键字会对可变引用进行解引用。
可变性必须与引用的可变性相匹配。
引用模式始终是不可拒绝的。
结构体模式
语法
结构体模式 :
表达式中路径{
结构体模式组 ?
}
结构体模式组 :
结构体模式字段组 (,
|,
结构体模式附加)?
| 结构体模式附加结构体模式字段组 :
结构体模式字段 (,
结构体模式字段) *结构体模式字段 :
外围属性 *
(
元组索引:
模式
| 标识符:
模式
|ref
?mut
? 标识符
)结构体模式附加 :
外围属性 *
..
结构体模式匹配满足其子模式定义的所有条件的结构体值。 可用于 解构 结构体。
在结构体模式中,可以通过名称、索引 (对于元组结构体) 引用字段,或通过使用 ..
忽略字段:
#![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 {..} => (), } }
如果没有使用 ..
,则需要匹配所有字段:
#![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
标识符 语法匹配任何值,并将其绑定到与给定字段同名的变量。
#![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 }
当结构体模式的任何子模式都是可拒绝的时,结构体模式就是可拒绝的。
元组结构模式
语法
元组结构模式 :
表达式中路径(
元组结构条目组?)
元组结构体模式匹配元组结构体和枚举值,匹配所有符合其子模式定义的条件。 可用于 解构 元组结构体或枚举值。
当子模式中有不可拒绝模式时,元组结构体模式就是可拒绝的。
元组模式
语法
元组模式 :
(
元组模式条目组?)
元组模式匹配满足其子模式定义的所有条件的元组值。可用于 解构 元组。
形式为 (..)
且具有单个 剩余模式 是一种特殊形式,不需要添加逗号,可以匹配任意大小的元组。
当其子模式中有一个可拒绝时,元组模式是可拒绝的。
以下是使用元组模式的示例:
#![allow(unused)] fn main() { let pair = (10, "ten"); let (a, b) = pair; assert_eq!(a, 10); assert_eq!(b, "ten"); }
分组模式
语法
分组模式 :
(
模式)
将一个模式括在括号中时,可以显式地控制复合模式的优先级。
例如,一个引用模式紧跟一个区间模式,如 &0..=5
将有歧义且不被允许,但可以用括号来表示。
#![allow(unused)] fn main() { let int_reference = &3; match int_reference { &(0..=5) => (), _ => (), } }
切片模式
语法
切片模式 :
[
切片模式条目组?]
切片模式可以匹配固定大小的数组和动态大小的切片。
#![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() { // 动态大小 let v = vec![1, 2, 3]; match v[..] { [a, b] => { /* 这个分支不会匹配,因为长度不匹配 */ } [a, b, c] => { /* 这个分支会匹配 */ } _ => { /* 这个通配符是必须的,因为长度不能在编译时确定 */ } }; }
切片模式在匹配数组时是不可拒绝的,只要每个元素都是不可拒绝的。
在匹配切片时,只有在形式为单个 ..
的 剩余模式 或 标识符模式 时才是不可拒绝的,其中 ..
剩余模式作为子模式。
在切片内,没有同时具有下限和上限的区间模式必须用括号括起来,例如 (a..)
,以明确其意图是匹配单个切片元素。
同时具有下限和上限的区间模式,例如 a..=b
,不需要用括号括起来。
路径模式
语法
路径模式 :
路径表达式
路径模式 是指引用常量值或没有字段的结构体或枚举变体的模式。
未经修饰的路径模式可以引用:
- 枚举变体
- 结构体
- 常量
- 关联常量
修饰的路径模式只能引用关联常量。常量不能是联合体类型。结构体和枚举常量必须有 #[derive(PartialEq, Eq)]
。
当路径模式引用结构体或枚举变体并且枚举只有一个变体或类型为不可拒绝类型的常量时,路径模式是不可拒绝的。
当路径模式引用可拒绝的常量或具有多个变体的枚举变体时,是可拒绝的。
或模式
或模式 是指可以匹配两个或更多子模式的模式 (例如 A | B | C
)。可以任意嵌套。
从语法上讲,或模式可以在任何其他模式允许的地方使用 (由 模式 产生式表示),但有一些例外情况,例如 let
绑定和函数和闭包参数 (由 模式非顶层项 产生式表示) 。
静态语义
-
对于任意的模式
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
,如果在其所处的深度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
的嵌套模式为止。请注意,"构造函数" 并不是指元组结构模式,而是指任何所产生类型的模式。这包括枚举变体、元组结构、具有命名字段的结构体、数组、元组和切片。
动态语义
- 在深度为
d
处使用模式c(p | q, ..rest)
匹配被匹配表达式e_s
的动态语义,其中c
是某个构造函数,p
和q
是任意模式,rest
可选地包含c
中的其他因子,其定义与c(p, ..rest) | c(q, ..rest)
相同。
与其他未限定模式的优先级
正如本章的其他地方所示,有几种语法上未限定的模式,包括标识符模式、引用模式和或模式。或模式始终具有最低的优先级。
这使我们可以为未来可能的类型注解功能保留语法空间,并减少歧义。
例如,x @ A(..) | B(..)
将导致错误,即 x
在所有模式中都未绑定。&A(x) | B(x)
将导致在不同的子模式中 x
类型不匹配的错误。