Rust-reference/patterns
Patterns
模式
commit: ff68c99b2b2003998b0f951fcb8dc290370a0275
本章译文最后维护日期:2020-11-25
句法
Pattern :
PatternWithoutRange
| RangePatternPatternWithoutRange :
LiteralPattern
| IdentifierPattern
| WildcardPattern
| RestPattern
| ObsoleteRangePattern
| ReferencePattern
| StructPattern
| TupleStructPattern
| TuplePattern
| GroupedPattern
| SlicePattern
| PathPattern
| MacroInvocation
模式基于给定数据结构去匹配值,并可选地将变量和这些结构中匹配到的值绑定起来。模式也用在变量声明上和函数(包括闭包)的参数上。
下面示例中的模式完成四件事:
- 测试
person是否在其car字段中填充了内容。 - 测试
person的age字段(的值)是否在 13 到 19 之间,并将其值绑定到给定的变量person_age上。 - 将对
name字段的引用绑定到给定变量person_name上。 - 忽略
person的其余字段。其余字段可以有任何值,并且不会绑定到任何变量上。
# 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);
}
模式用于:
Destructuring
解构
模式可用于解构结构体(struct)、枚举(enum)和元组。解构将一个值分解成它的组件组成,使用的句法与创建此类值时的几乎相同。在检验对象表达式的类型为结构体(struct)、枚举(enum)或元组(tuple)的模式中,占位符(_) 代表一个数据字段,而通配符 .. 代表特定变量/变体(variant)的所有剩余字段。当使用字段的名称(而不是字段序号)来解构数据结构时,允许将 fieldname 当作 fieldname: fieldname 的简写形式书写。
# 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);
}
};
Refutability
可反驳性
当一个模式有可能与它所匹配的值不匹配时,我们就说它是可反驳型的(refutable)。也就是说,不可反驳型(irrefutable)模式总是能与它们所匹配的值匹配成功。例如:
let (x, y) = (1, 2); // "(x, y)" 是一个不可反驳型模式
if let (a, 3) = (1, 2) { // "(a, 3)" 是可反驳型的, 将不会匹配
panic!("Shouldn't reach here");
} else if let (a, 4) = (3, 4) { // "(a, 4)" 是可反驳型的, 将会匹配
println!("Matched ({}, 4)", a);
}
Literal patterns
字面量模式
句法
LiteralPattern :
BOOLEAN_LITERAL
| CHAR_LITERAL
| BYTE_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
|-? INTEGER_LITERAL
|-? FLOAT_LITERAL
字面量模式匹配的值与字面量所创建的值完全相同。由于负数不是字面量,(特设定)字面量模式也接受字面量前的可选负号,它的作用类似于否定运算符。
浮点字面量目前还可以使用,但是由于它们在数值比较时带来的复杂性,在将来的 Rust 版本中,它们将被禁止用于字面量模式(参见 issue #41620)。
字面量模式总是可以反驳型的。
示例:
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"),
}
}
Identifier patterns
标识符模式
句法
IdentifierPattern :
ref?mut? IDENTIFIER (@Pattern ) ?
标识符模式将它们匹配的值绑定到一个变量上。此标识符在该模式中必须是唯一的。该变量会在作用域中遮蔽任何同名的变量。这种绑定的作用域取决于使用模式的上下文(例如 let绑定或匹配臂(match arm)[1])。
标识符模式只能包含一个标识符(也可能前带一个 mut),能匹配任何值,并将其绑定到该标识符上。最常见的标识符模式应用场景就是用在变量声明上和用在函数(包括闭包)的参数上。
let mut variable = 10;
fn sum(x: i32, y: i32) -> i32 {
# x + y
# }
要将模式匹配到的值绑定到变量上,也可使用句法 variable @ subpattern。例如,下面示例中将值 2 绑定到 e 上(不是整个区间(range):这里的区间是一个区间子模式(range subpattern))。
let x = 2;
match x {
e @ 1 ..= 5 => println!("got a range element {}", e),
_ => println!("anything"),
}
默认情况下,标识符模式里变量会和匹配到的值的一个拷贝副本绑定,或匹配值自身移动过来和变量完成绑定,具体是使用拷贝语义还是移动语义取决于匹配到的值是否实现了 Copy。也可以通过使用关键字 ref 将变量和值的引用绑定,或者使用 ref mut 将变量和值的可变引用绑定。示例:
# let a = Some(10);
match a {
None => (),
Some(value) => (),
}
match a {
None => (),
Some(ref value) => (),
}
在第一个匹配表达式中,值被拷贝(或移动)(到变量 value 上)。在第二个匹配中,对相同内存位置的引用被绑定到变量上。之所以需要这种句法,是因为在解构子模式(destructuring subpatterns)里,操作符 & 不能应用在值的字段上。例如,以下内容无效:
# 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 { }
要使其有效,请按如下方式编写代码:
# 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 不是被匹配的一部分。这里它唯一的目的就是使变量和匹配值的引用绑定起来,而不是潜在地拷贝或移动匹配到的内容。
路径模式(Path pattern)优先于标识符模式。如果给某个标识符指定了 ref 或 ref mut,同时该标识符又遮蔽了某个常量,这会导致错误。
如果 @子模式是不可反驳型的或未指定子模式,则标识符模式是不可反驳型的。
Binding modes
绑定方式
基于人类工程学的考虑,为了让引用和匹配值的绑定更容易一些,模式会自动选择不同的绑定方式。当引用值与非引用模式匹配时,这将自动地被视为 ref 或 ref mut 绑定方式。示例:
let x: &Option<i32> = &Some(3);
if let Some(y) = x {
// y 被转换为`ref y` ,其类型为 &i32
}
非引用模式(Non-reference patterns)包括除上面这种绑定模式和后面会讲到的通配符模式(_)、匹配引用类型的常量(const)模式和引用模式这些模式以外的所有模式。
如果绑定模式(binding pattern)中没有显式地包含 ref、ref mut、mut,那么它将使用默认绑定方式来确定如何绑定变量。默认绑定方式以使用移动语义的“移动(move)”方式开始。当匹配一个模式时,编译器对模式从外到内逐层匹配。每次非引用模式和引用匹配上了时,引用都会自动解引用出最后的值,并更新默认绑定方式,再进行最终的匹配。此时引用会将默认绑定方式设置为 ref 方式。可变引用会将模式设置为 ref mut 方式,除非绑定方式已经是 ref 了(在这种情况下它仍然是 ref 方式)。如果自动解引用解出的值仍然是引用,则会重复解引用。[2]
移动语义的绑定方式和引用语义的绑定方式可以在同一个模式中混合使用,这样做会导致绑定对象的部分被移走,并且之后无法再使用该对象。这只适用于类型无法拷贝的情况下。
下面的示例中,name 被移出了 person,因此如果再试图把 person 作为一个整体使用,或再次使用 person.name,将会因为部分移出(partial move)的问题而报错。
示例:
# struct Person {
# name: String,
# age: u8,
# }
# let person = Person{ name: String::from("John"), age: 23 };
// 在 `age` 被引用绑定的情况下,`name` 被从 person 中移出
let Person { name, ref age } = person;
Wildcard pattern
通配符模式
句法
WildcardPattern :
_
通配符模式(下划线符号)能与任何值匹配。常用它来忽略那些无关紧要的值。在其他模式中使用该模式时,它匹配单个数据字段(与和匹配所有其余字段的 .. 相对)。与标识符模式不同,它不会复制、移动或借用它匹配的值。
示例:
# 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 {}
通配符模式总是不可反驳型的。
Rest patterns
剩余模式
句法
RestPattern :
..
剩余模式(.. token)充当匹配长度可变的模式(variable-length pattern),它匹配之前之后没有匹配的零个或多个元素。它只能在元组模式、元组结构体模式和切片模式中使用,并且只能作为这些模式中的一个元素出现一次。当作为标识符模式的子模式时,它也可出现在切片模式里。
剩余模式总是不可反驳型的。
示例:
# 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),
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) => println!("tail must be 5"),
(..) => println!("matches everything else"),
}
Range patterns
区间模式
句法
RangePattern :
RangePatternBound..=RangePatternBoundObsoleteRangePattern :(译者注:废弃的区间模式句法/产生式) RangePatternBound
...RangePatternBoundRangePatternBound :
CHAR_LITERAL
| BYTE_LITERAL
|-? INTEGER_LITERAL
|-? FLOAT_LITERAL
| PathInExpression
| QualifiedPathInExpression
区间模式匹配在其上下边界定义的封闭区间内的值。例如,一个模式 'm'..='p' 将只能匹配值'm'、'n'、'o' 和 'p'。它的边界值可以是字面量,也可以是指向常量值的路径。
一个模式 a ..= b 必须总是有 a ≤ b。例如,10..=0 这样的区间模式是错误的。
保留 ...句法只是为了向后兼容。
区间模式只适用于标量类型(scalar type)。可接受的类型有:
- 整型(u8、i8、u16、i16、usize、isize …)。
- 字符型(char)。
- 浮点类型(f32 和 f64)。这已被弃用,在未来版本的 Rust 中将不可用(参见 issue #41620)。
示例:
# 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!(),
});
// 使用指向常量值的路径:
# 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!("这适用并占用{}个字节", 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;
# }
// 使用限定路径:
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",
});
当区间模式匹配某(非usize 和 非isize)整型类型和字符型(char)的整个值域时,此模式是不可反驳型的。例如,0u8..=255u8 是不可反驳型的。某类整型的值区间是从该类型的最小值到该类型最大值的闭区间。字符型(char)的值的区间就是那些包含所有 Unicode 标量值的区间,即 '\u{0000}'..='\u{D7FF}' 和 '\u{E000}'..='\u{10FFFF}'。
Reference patterns
引用模式
句法
ReferencePattern :
(&|&&)mut? PatternWithoutRange
引用模式对当前匹配的指针做解引用,从而能借用它们:
例如,下面 x: &i32 上的两个匹配是等效的:
let int_reference = &3;
let a = match *int_reference { 0 => "zero", _ => "some" };
let b = match int_reference { &0 => "zero", _ => "some" };
assert_eq!(a, b);
引用模式的文法产生式(grammar production)要求必须使用 token && 来匹配对引用的引用,因为 && 本身是一个单独的 token,而不是两个 & token。
译者注:举例
let a = Some(&&10); match a { Some( &&value ) => println!("{}", value), None => {} }
引用模式中添加关键字 mut 可对可变引用做解引用。引用模式中的可变性标记必须与作为匹配对象的那个引用的可变性匹配。
引用模式总是不可反驳型的。
Struct patterns
结构体模式
句法
StructPattern :
PathInExpression{
StructPatternElements ?
}StructPatternElements :
StructPatternFields (,|,StructPatternEtCetera)?
| StructPatternEtCeteraStructPatternFields :
StructPatternField (,StructPatternField) *StructPatternField :
OuterAttribute *
(
TUPLE_INDEX:Pattern
| IDENTIFIER:Pattern
|ref?mut? IDENTIFIER
)StructPatternEtCetera :
OuterAttribute *
..
结构体模式匹配与子模式定义的所有条件匹配的结构体值。它也被用来解构结构体。
在结构体模式中,结构体字段需通过名称、索引(对于元组结构体来说)来指代,或者通过使用 .. 来忽略:
# 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} => (), // 顺序没关系
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} => (), // 顺序没关系
PointTuple {0: 10, ..} => (),
PointTuple {..} => (),
}
如果没使用 ..,需要提供所有字段的详尽匹配:
# 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 这样的句法格式能匹配任意值,并将其绑定到与给定字段同名的变量上。
# 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; // 解构所有的字段
当一个结构体模式的子模式是可反驳型的,那这个结构体模式就是可反驳型的。
Tuple struct patterns
元组结构体模式
句法
TupleStructPattern :
PathInExpression(TupleStructItems?)
元组结构体模式匹配元组结构体值和枚举值,这些值将与该模式的子模式定义的所有条件进行匹配。它还被用于解构元组结构体值或枚举值。
当元组结构体模式的一个子模式是可反驳型的,则该元组结构体模式就是可反驳型的。
Tuple patterns
元组模式
句法
TuplePattern :
(TuplePatternItems?)TuplePatternItems :
Pattern,
| RestPattern
| Pattern (,Pattern)+,?
元组模式匹配与子模式定义的所有条件匹配的元组值。它们还被用来解构元组值。
内部只带有一个剩余模式(RestPattern)的元组句法形式 (..) 是一种内部不需要逗号分割的特殊匹配形式,它可以匹配任意长度的元组。
当元组模式的一个子模式是可反驳型的,那该元组模式就是可反驳型的。
使用元组模式的示例:
let pair = (10, "ten");
let (a, b) = pair;
assert_eq!(a, 10);
assert_eq!(b, "ten");
Grouped patterns
分组模式
句法
GroupedPattern :
(Pattern)
将模式括在圆括号内可用来显式控制复合模式的优先级。例如,像 &0..=5 这样的引用模式和区间模式相邻就会引起歧义,这时可以用圆括号来消除歧义。
let int_reference = &3;
match int_reference {
&(0..=5) => (),
_ => (),
}
Slice patterns
切片模式
句法
SlicePattern :
[SlicePatternItems?]
切片模式可以匹配固定长度的数组和动态长度的切片。
// 固定长度
let arr = [1, 2, 3];
match arr {
[1, _, _] => "从 1 开始",
[a, b, c] => "从其他值开始",
};
// 动态长度
let v = vec![1, 2, 3];
match v[..] {
[a, b] => { /* 这个匹配臂不适用,因为长度不匹配 */ }
[a, b, c] => { /* 这个匹配臂适用 */ }
_ => { /* 这个通配符是必需的,因为长度不是编译时可知的 */ }
};
在匹配数组时,只要每个元素是不可反驳型的,切片模式就是不可反驳型的。当匹配切片时,只有单个 .. 剩余模式或带有 ..(剩余模式)作为子模式的标识符模式的情况才是不可反驳型的。
Path patterns
路径模式
句法
PathPattern :
PathInExpression
| QualifiedPathInExpression
路径模式是指向(refer to)常量值或指向没有字段的结构体或没有字段的枚举变体的模式。
非限定路径模式可以指向:
- 枚举变体
- 结构体
- 常量
- 关联常量
限定路径模式只能指向关联常量。
常量不能是联合体类型。结构体常量和枚举常量必须带有 #[derive(PartialEq, Eq)] 属性(不只是实现)。
当路径模式指向结构体或枚举变体(枚举只有一个变体)或类型为不可反驳型的常量时,该路径模式是不可反驳型的。当路径模式指向的是可反驳型常量或带有多个变体的枚举时,该路径模式是可反驳型的。
- ↑ 请仔细参研匹配表达式中的 MatchExpression产生式,搞清楚匹配臂(MatchArm)的位置。
- ↑ 文字叙述有些晦涩,译者举个例子:假如
if let &Some(y) = &&&Some(3) {,此时会首先剥掉等号两边的第一层&号,然后是Some(y)和&&Some(3)匹配,此时发现是非引用模式和引用匹配上了,就再对&&Some(3)做重复解引用,解出Some(3),然后从外部转向内部,见到最后的变量y和检验对象3,就更新y的默认绑定方式为ref,所以y就匹配为&3;如果我们这个例子的变量y改为ref y,不影响y的绑定效果;极端的情况if let &Some(y) = &&&Some(x) {,如果x是可变的,那么此时y的绑定方式就是ref mut,再进一步极端if let &Some(ref y) = &&&Some(x) {,此时y的绑定方式仍是ref。