一、模式是什么
模式是Rust中特殊的语法,它用来匹配值
二、模式的使用场景
(一)match
match的每个分支箭头左边部分就是模式。
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
例子
match x {
None => None,
Some(i) => Some(i + 1),
}
箭头左边的None和Some(i)就是模式。
(二)if let
例子
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {color}, as the background");
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
if let后面的Some(color) 和 Ok(age)就是模式。
如果用户指定了中意的颜色,就使用它。如果没有指定且今天是星期二,就使用绿色。如果用户指定了他们的年龄并能够成功解析为数字的话,我们将根据这个数字使用紫色或者橙色。最后,如果没有一个条件符合,就使用蓝色。
(三)while let
例子
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
while let后面的Some(top) 就是模式。
pop方法取出vector的最后一个元素并返回Some(value)。如果vector是空的,它返回None。while循环只要pop返回Some就会一直运行其块中的代码。一旦其返回None,while循环停止。我们可以使用while let来弹出栈中的每一个元素。
(四)for
例子
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
运行结果
a is at index 0
b is at index 1
c is at index 2
(index, value)就是模式
enumerate方法返回一个元组,元组元素分别是索引和值。第一个是元组 (0, ‘a’)。当这个值匹配模式 (index, value)时,index将会是0而value将会是 ‘a’
(五)let
let PATTERN = value;
例子
let x = 5;
x就是一个模式。
let (x, y, z) = (1, 2, 3);
(x, y, z) 也是一个模式。Rust会比较值 (1, 2, 3) 与模式 (x, y, z) 并发现此值匹配这个模式。在这个例子中,将会把1绑定到x,2绑定到y并将3绑定到z。
如果模式中元素的数量不匹配值中元素的数量,则整个类型不匹配,并会得到一个编译错误。
let (x, y) = (1, 2, 3);
一个错误的模式结构
(六)函数参数
函数参数也可以是模式。
例子
fn foo(x: i32) {
// code goes here
}
x就是一个模式!类似于let中的模式。
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
这会打印出Current location: (3, 5)。值 &(3, 5) 会匹配模式 &(x, y),如此x得到了值3,而y得到了值5。
(七)闭包参数
闭包参数也可以是模式。与函数参数一样。
三、refutable与irrefutable模式的区别
模式有两种:refutable(可反驳的)和irrefutable(不可反驳的)。
能匹配任何值的模式称为不可反驳的(irrefutable)。一个例子是let x = 5; 语句中的x,因为x可以匹配任何值所以不可能会失败。
不能匹配任何值的的模式称为可反驳的(refutable)。一个例子是if let Some(x) = a_value表达式中的Some(x);如果变量a_value中的值是None,那么Some(x) 模式不能匹配。
函数参数、let、for只能使用不可反驳的模式
if let和while let只能使用可反驳的模式,因为根据定义他们意在处理可能的失败:条件表达式的功能就是根据成功或失败执行不同的操作。
let Some(x) = some_option_value;
尝试在let中使用可反驳模式,编译错误
Some(x)是可反驳的。如果some_option_value的值是None,其不会匹配模式Some(x)。
可以使用if let。如此,如果模式不匹配,大括号中的代码将被忽略,其余代码保持有效。
if let Some(x) = some_option_value {
println!("{}", x);
}
尝试把不可反驳模式用到if let上,编译器会给出一个警告:
if let x = 5 {
println!("{}", x);
};
match必须使用可反驳模式,除了最后一个分支需要使用能匹配任何剩余值的不可反驳模式。
四、模式语法
(一)字面量模式
句法
LiteralPattern :
BOOLEAN_LITERAL
| CHAR_LITERAL
| BYTE_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
| -? INTEGER_LITERAL
字面量模式能匹配字面量。
字面量模式总是可反驳型的。
示例:
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"),
}
}
-1、1、2、4都是字面量模式
(二)标识符模式
句法
IdentifierPattern :
ref? mut? IDENTIFIER (@ Pattern ) ?
标识符模式只能包含一个标识符,能匹配任何值,并将值绑定到该标识符上。
标识符模式总是不可反驳型的。
最常见的标识符模式应用场景就是用在let上和函数(包括闭包)的参数上。
let mut variable = 10;
fn sum(x: i32, y: i32) -> i32 {
默认情况下,标识符模式里变量会和匹配到的值的一个副本绑定,或值自身移动过来和变量绑定,具体是使用拷贝语义还是移动语义取决于匹配到的值是否实现了 Copy。也可以使用关键字ref将变量和值的引用绑定,或者使用ref mut将变量和值的可变引用绑定。示例:
match a {
None => (),
Some(value) => (),
}
match a {
None => (),
Some(ref value) => (),
}
在第一个匹配表达式中,值被拷贝或移动到变量value上。在第二个匹配中,值的引用被绑定到变量上。之所以需要这种句法,是因为在解构时,操作符 & 不能应用在值的字段上。例如,以下内容无效:
if let Person { name: &person_name, age: 18..=150 } = value { }
要使其有效,请按如下方式编写代码:
if let Person { name: ref person_name, age: 18..=150 } = value { }
这里,ref不是被匹配的一部分。它唯一的目的就是使变量和匹配值的引用绑定起来,而不是潜在地拷贝或移动匹配到的内容。
@ 模式
@允许我们在创建一个变量的同时测试其值是否匹配模式。使用 @ 可以在一个模式中同时测试和保存变量值。
示例
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello { id: id_variable @ 3..=7,} => println!("Found an id in range: {}", id_variable),
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Found some other id: {}", id),
}
上例会打印出Found an id in range: 5。
第一个分支只有id的值在3到7之间,才匹配此模式,才将id的值赋给id_variable。
第二个分支只在模式中指定了一个范围,id字段的值可以是10、11或12,不过这个分支不能使用id的值,因为没有将id值保存进一个变量。
最后一个分支使用了结构体字段简写语法,此分支没有测试,任何值都会匹配此分支。因此可以使用id的值。
(三)通配符模式
句法
WildcardPattern :
_
通配符模式(下划线符号)能与任何值匹配。常用它来忽略那些无关紧要的值。在其他模式中使用该模式时,它匹配单个数据字段(与和匹配所有其余字段的..
相对)。与标识符模式不同,它不会复制、移动或借用它匹配的值。
通配符模式总是不可反驳型的。
示例:
let (a, _) = (10, x); // x一定会被 _ 匹配上
let real_part = |a: f64, _: f64| { a };// 忽略一个函数/闭包参数
let RGBA{r: red, g: green, b: blue, a: _} = color;// 忽略结构体的一个字段
if let Some(_) = x {}// 能接收带任何值的任何Some
(四)剩余模式
句法
RestPattern :
..
剩余模式匹配之前之后没有匹配的零个或多个元素。它只能在元组模式、元组结构体模式、切片模式中使用,并且只能作为这些模式中的一个元素出现一次。当作为标识符模式的子模式时,它也可出现在切片模式里。
剩余模式总是不可反驳型的。
示例:
match value {
[] => 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);
}
// 剩余模式也可是在元组和元组结构体模式中使用。
match tuple {
(1, .., y, z) => println!("y={} z={}", y, z),
(.., 5) => println!("tail must be 5"),
(..) => println!("matches everything else"),
}
(五)区间模式
句法
RangePattern :
RangePatternBound ..= RangePatternBound
RangePatternBound :
CHAR_LITERAL
| BYTE_LITERAL
| -? INTEGER_LITERAL
| PathInExpression
| QualifiedPathInExpression
区间模式匹配封闭区间内的值。例如,一个模式'm'..='p'
将只能匹配值’m’、‘n’、‘o’ 和 ‘p’。它的边界值可以是字面量,也可以是指向常量值的路径。
一个模式a ..= b
必须总是a≤b。例如,10..=0
这样的区间模式是错误的。
区间模式只适用于标量类型。可接受的类型有:
整型(u8、i8、u16、i16、usize、isize ...)。
字符型(char)。
示例:
let valid_variable = match c {
'a'..='z' => true,
'A'..='Z' => true,
'α'..='ω' => true,
_ => false,
};
println!("{}", match ph {
0..=6 => "acid",
7 => "neutral",
8..=14 => "base",
_ => unreachable!(),
});
// 使用指向常量值的路径:
println!("{}", match altitude {
TROPOSPHERE_MIN..=TROPOSPHERE_MAX => "troposphere",
STRATOSPHERE_MIN..=STRATOSPHERE_MAX => "stratosphere",
MESOSPHERE_MIN..=MESOSPHERE_MAX => "mesosphere",
_ => "outer space, maybe",
});
if let size @ binary::MEGA..=binary::GIGA = n_items * bytes_per_item {
println!("这适用并占用{}个字节", size);
}
// 使用路径:
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}'
。
(六)引用模式
句法
ReferencePattern :
(&|&&) mut? PatternWithoutRange
引用模式对匹配到的指针做解引用
例如,下面两个匹配是等效的:
let int_reference = &3;
let a = match *int_reference { 0 => "zero", _ => "some" };
let b = match int_reference { &0 => "zero", _ => "some" };
assert_eq!(a, b);
引用模式要求必须使用&& 来匹配对引用的引用,因为 && 本身是一个单独的token,而不是两个 token。
举例
let a = Some(&&10);
match a {
Some( &&value ) => println!("{}", value),
None => {}
}
引用模式中添加关键字mut可对可变引用做解引用。引用模式中的可变性标记必须与值的引用的可变性匹配。
引用模式总是不可反驳型的。
(七)结构体模式
句法
StructPattern :
PathInExpression {
StructPatternElements ?
}
StructPatternElements :
StructPatternFields (, | , StructPatternEtCetera)?
| StructPatternEtCetera
StructPatternFields :
StructPatternField (, StructPatternField) *
StructPatternField :
OuterAttribute *
(
TUPLE_INDEX : Pattern
| IDENTIFIER : Pattern
| ref? mut? IDENTIFIER
)
StructPatternEtCetera :
OuterAttribute *
..
结构体模式匹配结构体值。它也被用来解构结构体。
在结构体模式中,结构体字段需通过名称、索引(对于元组结构体来说)来指代,或者通过使用..
来忽略:
match s {
Point {x: 10, y: 20} => (),
Point {y: 10, x: 20} => (), // 顺序没关系
Point {x: 10, ..} => (),
Point {..} => (),
}
match t {
PointTuple {0: 10, 1: 20} => (),
PointTuple {1: 10, 0: 20} => (), // 顺序没关系
PointTuple {0: 10, ..} => (),
PointTuple {..} => (),
}
如果没使用 ..,需要提供所有字段的详尽匹配:
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: _} => (),
}
IDENTIFIER能匹配任意值,并将其绑定到变量上。
let Struct{a: x, b: y, c: z} = struct_value; // 解构所有的字段
当一个结构体模式的子模式是可反驳型的,那这个结构体模式就是可反驳型的。
解构结构体
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
}
或者
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}
这段代码创建了变量x和y,与变量p中的x和y相匹配。其结果是变量x和y包含结构体p中的值。
也可以使用字面值作为结构体模式的一部分进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
例子
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("On the x axis at {x}"),
Point { x: 0, y } => println!("On the y axis at {y}"),
Point { x, y } => {
println!("On neither axis: ({x}, {y})");
}
}
}
第一个分支通过指定字段y匹配字面值0来匹配任何位于x轴上的点。此模式仍然创建了变量x以便在分支的代码中使用。
第二个分支通过指定字段x匹配字面值0来匹配任何位于y轴上的点,并为字段y创建了变量y。
第三个分支没有指定任何字面值,所以其会匹配任何其他的Point并为x和y两个字段创建变量。
在这个例子中,值p因为其x包含0而匹配第二个分支,因此会打印出On the y axis at 7。
记住match表达式一旦找到一个匹配的模式就会停止检查其它分支,所以即使Point { x: 0, y: 0} 在x轴上也在y轴上,这些代码也只会打印On the x axis at 0。
(八)元组结构体模式
句法
TupleStructPattern :
PathInExpression ( TupleStructItems? )
TupleStructItems :
Pattern ( , Pattern )* ,?
元组结构体模式匹配元组结构体值和枚举值。它还被用于解构元组结构体值或枚举值。
当元组结构体模式的一个子模式是可反驳型的,则该元组结构体模式就是可反驳型的。
(九)元组模式
句法
TuplePattern :
( TuplePatternItems? )
TuplePatternItems :
Pattern ,
| RestPattern
| Pattern (, Pattern)+ ,?
元组模式匹配元组值。它们还被用来解构元组值。
内部只带一个剩余模式的元组模式(..)
可以匹配任意长度的元组。
当元组模式的一个子模式是可反驳型的,那该元组模式就是可反驳型的。
使用元组模式的示例:
let pair = (10, "ten");
let (a, b) = pair;
assert_eq!(a, 10);
assert_eq!(b, "ten");
(十)切片模式
句法
SlicePattern :
[ SlicePatternItems? ]
SlicePatternItems :
Pattern (, Pattern)* ,?
切片模式可以匹配固定长度的数组和动态长度的切片。
// 固定长度
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] => { /* 这个分支适用 */ }
_ => { /* 这个通配符是必需的,因为长度不是编译时可知的 */ }
};
在匹配数组时,只要每个元素是不可反驳型的,切片模式就是不可反驳型的。当匹配切片时,只有单个..
或带有 ..
作为子模式的标识符模式的情况才是不可反驳型的。
(十一)路径模式
句法
PathPattern :
PathInExpression
| QualifiedPathInExpression
路径模式是指向常量值或指向没有字段的结构体或没有字段的枚举变体的模式。
非限定路径模式可以指向:
枚举变体
结构体
常量
关联常量
限定路径模式只能指向关联常量。
常量不能是联合体类型。结构体常量和枚举常量必须带有 #[derive(PartialEq, Eq)] 属性(不只是实现)。
当路径模式指向结构体或枚举变体(枚举只有一个变体)或类型为不可反驳型的常量时,该路径模式是不可反驳型的。当路径模式指向的是可反驳型常量或带有多个变体的枚举时,该路径模式是可反驳型的。
解构枚举
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.");
}
Message::Move { x, y } => {
println!("Move in the x direction {x} and in the y direction {y}");
}
Message::Write(text) => {
println!("Text message: {text}");
}
Message::ChangeColor(r, g, b) => {
println!("Change the color to red {r}, green {g}, and blue {b}",)
}
}
}
这段代码会打印出Change the color to red 0, green 160, and blue 255。尝试改变msg的值来观察其他分支代码的运行。
对于像Message::Quit这样没有任何数据的枚举成员,不能进一步解构其值。只能匹配其字面值Message::Quit,因此模式中没有任何变量。
对于像Message::Move这样的类结构体枚举成员,可以采用类似于匹配结构体的模式。在成员名称后,使用大括号并列出字段变量以便将其分解以供此分支的代码使用。这里使用了示例18-13所展示的简写。
对于像Message::Write这样的包含一个元素,以及像Message::ChangeColor这样包含三个元素的类元组枚举成员,其模式则类似于用于解构元组的模式。模式中变量的数量必须与成员中元素的数量一致。
解构嵌套的结构体和枚举
目前为止,所有的例子都只匹配了深度为一级的结构体或枚举,不过当然也可以匹配嵌套的项!例如,
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color),
}
fn main() {
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
match msg {
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!("Change color to hue {h}, saturation {s}, value {v}")
}
_ => (),
}
}
第一个分支的模式匹配一个包含Color::Rgb枚举成员的Message::ChangeColor枚举成员,然后模式绑定了3个内部的i32值。
第二个分支的模式也匹配一个Message::ChangeColor枚举成员,但是其内部的枚举会匹配Color::Hsv枚举成员。
我们可以在一个match表达式中指定这些复杂条件,即使会涉及到两个枚举。
(十二)分组
句法
GroupedPattern :
( Pattern )
将模式括在圆括号内可用来显式控制模式的优先级。例如,像 &0..=5
这样的引用模式和区间模式相邻就会引起歧义,这时可以用圆括号来消除歧义。
let int_reference = &3;
match int_reference {
&(0..=5) => (),
_ => (),
}
(十三)并列
pattern | pattern
匹配两个或多个并列子模式(例如:A | B | C)中的一个的模式。除了let绑定和函数参数(包括闭包参数),可以在任何场景使用