模式语法
在本节中,我们收集了模式中所有有效的语法,并讨论了原因和 何时您可能想要使用每一个。
匹配文本
正如你在第 6 章中看到的,你可以直接将模式与文本匹配。这 以下代码给出了一些示例:
fn main() { let x = 1; match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), _ => println!("anything"), } }
此代码打印one
因为x
为 1。此语法非常有用
当你希望代码在获得特定具体
价值。
匹配命名变量
命名变量是匹配任何值的无可辩驳的模式,我们已经使用了
他们在书中多次出现。但是,当您使用
命名变量match
表达 式。因为match
启动新范围,
变量声明为模式的一部分,在match
表达式将
shadow 那些在match
构造,就像
替换为所有变量。在示例 18-11 中,我们声明了一个名为x
使用
价值Some(5)
和一个变量y
使用值10
.然后,我们创建一个match
value 上的 expressionx
.查看 match arms 和println!
,并尝试弄清楚代码之前将打印什么
运行此代码或进一步阅读。
文件名: src/main.rs
fn main() { let x = Some(5); let y = 10; match x { Some(50) => println!("Got 50"), Some(y) => println!("Matched, y = {y}"), _ => println!("Default case, x = {x:?}"), } println!("at the end: x = {x:?}, y = {y}"); }
示例 18-11:一个match
表达式替换为
引入 shadowed 变量y
我们来看看当match
表达式运行。模式
在第一个匹配项 ARM 中,与定义的x
,因此代码
继续。
第二个匹配臂中的模式引入了一个名为y
那
将匹配Some
价值。因为我们在一个新的范围内
这match
表达式,这是一个新的y
变量,而不是y
我们在
以值 10 开头。这个新的y
binding 将匹配任何值
在Some
,这就是我们在x
.因此,这个新的y
绑定到
的Some
在x
.该值为5
,因此
该 arm 执行并打印Matched, y = 5
.
如果x
曾是None
value 而不是Some(5)
中,第一个
两个 Arms 不匹配,因此该值将与
强调。我们没有引入x
变量在
underscore arm,因此x
在表达式中仍然是外部的x
那没有
被遮蔽。在这个假设的情况下,match
将打印Default case, x = None
.
当match
expression 完成,则其范围结束,则
内部y
.最后println!
生产at the end: x = Some(5), y = 10
.
要创建match
表达式,该表达式比较外部x
和y
,而不是引入阴影变量,我们需要使用 match
guard 条件。我们将在后面的“Extra
Conditionals with Match Guards“ 部分。
多种模式
在match
表达式中,您可以使用|
语法
这是 pattern 或 operator。例如,在下面的代码中,我们将
的值x
针对匹配分支,其中第一个分支具有 OR 选项,
这意味着如果x
匹配该 Arm 中的任一值,则
ARM 的代码将运行:
fn main() { let x = 1; match x { 1 | 2 => println!("one or two"), 3 => println!("three"), _ => println!("anything"), } }
此代码打印one or two
.
将值范围与..=
这..=
syntax 允许我们匹配一个包含的值范围。在
以下代码中,当模式与给定
range,该 arm 将执行:
fn main() { let x = 5; match x { 1..=5 => println!("one through five"), _ => println!("something else"), } }
如果x
是 1、2、3、4 或 5,则第一个分支将匹配。此语法更
比使用|
运算符来表示
同样的想法;如果我们要使用|
我们必须指定1 | 2 | 3 | 4 | 5
.
指定范围要短得多,特别是如果我们想匹配,比如说,任何
数字在 1 到 1,000 之间!
编译器在编译时检查范围是否不为空,并且由于
只有 Rust 可以判断范围是否为空的类型是char
和
numeric 值,范围只允许与 numeric 或char
值。
下面是一个使用 范围char
值:
fn main() { let x = 'c'; match x { 'a'..='j' => println!("early ASCII letter"), 'k'..='z' => println!("late ASCII letter"), _ => println!("something else"), } }
Rust 可以看出这一点'c'
在第一个图案的范围内并打印early ASCII letter
.
解构以分离值
我们还可以使用 patterns 来解构结构体、枚举和元组以使用 这些值的不同部分。让我们来看看每个值。
解构结构体
示例 18-12 显示了一个Point
struct 中具有两个字段,x
和y
,我们可以
使用带有let
陈述。
文件名: src/main.rs
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); }
示例 18-12:将结构体的 fields 解构为 分隔变量
此代码创建变量a
和b
与x
和y
字段的p
结构。此示例显示
模式中的变量不必与结构体的字段名称匹配。
但是,通常将变量名称与字段名称匹配以使其
更容易记住哪些变量来自哪些字段。正因为如此
常见用法,并且因为编写let Point { x: x, y: y } = p;
包含一个
大量重复,Rust 有一个匹配结构体字段的模式的简写:
您只需列出 struct 字段的名称和创建的变量
将具有相同的名称。示例 18-13 的行为相同
方式与示例 18-12 中的代码一样,但是在let
pattern 为x
和y
而不是a
和b
.
文件名: src/main.rs
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); }
示例 18-13:使用 struct 解构 struct 字段 字段速记
此代码创建变量x
和y
匹配x
和y
领域
的p
变量。结果是变量x
和y
包含
值p
结构。
我们还可以使用 Literal 值作为结构体模式的一部分进行解构 而不是为所有字段创建变量。这样做可以让我们测试 创建变量时,特定值的一些字段 解构其他字段。
在示例 18-14 中,我们有一个match
分隔Point
值
分为三种情况:直接位于x
axis (当y = 0
)、在y
轴 (x = 0
),或者两者都不是。
文件名: src/main.rs
struct Point { x: i32, y: i32, } 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})"); } } }
示例 18-14:解构和匹配 Literals 值 在一个模式中
第一个分支将匹配位于x
轴,方法是指定
这y
如果 field 的值与文本匹配,则 field 匹配0
.模式依然
创建一个x
变量,我们可以在此 ARM 的代码中使用。
同样,第二个分支与y
轴,方法是指定
这x
field 如果其值为0
并创建一个变量y
对于
的值y
田。第三个分支没有指定任何文本,因此它
匹配任何其他Point
并为x
和y
领域。
在此示例中,值p
凭借x
包含 0,因此此代码将打印On the y axis at 7
.
请记住,一个match
表达式在找到
第一个匹配模式,因此即使Point { x: 0, y: 0}
位于x
轴
和y
axis 的 Axis 中,此代码只会打印On the x axis at 0
.
解构枚举
我们在本书中解构了枚举(例如,第 6 章中的示例 6-5),
但尚未明确讨论解构枚举的模式
对应于定义存储在枚举中的数据的方式。作为
例如,在示例 18-15 中,我们使用了Message
enum 并写入
一个match
替换为将解构每个内部值的模式。
文件名: src/main.rs
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}") } } }
示例 18-15:解构持有 不同类型的值
此代码将打印Change the color to red 0, green 160, and blue 255
.尝试
更改msg
以查看来自其他 ARM 运行的代码。
对于没有任何数据的枚举变体,例如Message::Quit
,我们无法解构
值 再进一步。我们只能匹配字面量Message::Quit
价值
并且没有变量处于该模式中。
对于类似结构体的枚举变体,例如Message::Move
,我们可以使用
类似于我们指定用于匹配结构体的模式。在变体名称之后,我们
放置大括号,然后列出带有变量的字段,以便我们分开
此 Arm 的代码中使用的 pieces。这里我们使用简写形式为
我们在示例 18-13 中这样做了。
对于类似元组的枚举变体,如Message::Write
,其中包含一个
元素和Message::ChangeColor
,它包含一个包含三个元素的元组,
pattern 类似于我们指定用于匹配 Tuples 的模式。的
模式中的变量必须与 Variety 中的元素数量匹配
匹配。
解构嵌套结构体和枚举
到目前为止,我们的例子都是匹配一级深的结构体或枚举,
但是 Matching 也适用于嵌套项目!例如,我们可以重构
示例 18-15 中的代码在ChangeColor
message 中,如示例 18-16 所示。
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}") } _ => (), } }
示例 18-16:匹配嵌套枚举
第一个分支在match
表达式与Message::ChangeColor
enum 变体,其中包含Color::Rgb
变体;然后
模式绑定到三个内部i32
值。第二个
arm 还会匹配Message::ChangeColor
enum 变体,但内部枚举
比赛Color::Hsv
相反。我们可以在一个match
expression,即使涉及两个枚举。
解构结构和元组
我们可以以更复杂的方式混合、匹配和嵌套解构模式。 以下示例显示了一个复杂的解构,其中我们将结构体和 tuples 并解构所有 primitive 值:
fn main() { struct Point { x: i32, y: i32, } let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 }); }
这段代码让我们将复杂类型分解为它们的组成部分,以便我们可以使用 我们感兴趣的值。
使用 patterns 解构是使用值片段的便捷方式,例如 作为结构体中每个字段的值,彼此分开。
忽略模式中的值
您已经看到,忽略模式中的值有时很有用,例如
在match
,以获得一个实际上并不重要的
Anything but 不考虑所有剩余的可能值。有一些
忽略模式中整个值或部分值的方法:使用模式(您已经见过),在另一个模式中使用模式,
使用以下划线开头的名称,或使用_
_
..
忽略剩余
值的一部分。让我们探讨一下如何以及为什么使用这些模式。
忽略 Entire Value 和_
我们使用下划线作为通配符模式,它将匹配除
not 绑定到值。这尤其有用,因为match
表达式,但我们也可以在任何模式中使用它,包括函数
参数,如示例 18-17 所示。
文件名: src/main.rs
fn foo(_: i32, y: i32) { println!("This code only uses the y parameter: {y}"); } fn main() { foo(3, 4); }
示例 18-17:在函数签名中使用_
此代码将完全忽略该值3
作为第一个参数传递,
并将打印This code only uses the y parameter: 4
.
在大多数情况下,当您不再需要特定的函数参数时,您可以 将更改签名,使其不包含未使用的参数。忽略 函数参数在以下情况下特别有用,例如, 当你需要某个类型签名时,你正在实现一个 trait,但 function body 不需要任何参数。你 然后避免收到有关未使用函数参数的编译器警告,因为 如果你用一个名字来代替。
忽略具有 Nested 的值的部分_
我们还可以在 inside another pattern 中使用来忽略值的一部分,因为
例如,当我们只想测试值的一部分,但不想使用
我们想要运行的相应代码中的其他部分。示例 18-18 显示了代码
负责管理设置的值。业务要求是
不应允许用户覆盖
设置,但可以取消设置设置,并在当前未设置时为其指定一个值。_
fn main() { let mut setting_value = Some(5); let new_setting_value = Some(10); match (setting_value, new_setting_value) { (Some(_), Some(_)) => { println!("Can't overwrite an existing customized value"); } _ => { setting_value = new_setting_value; } } println!("setting is {setting_value:?}"); }
示例 18-18:在
火柴Some
变体,当我们不需要使用Some
此代码将打印Can't overwrite an existing customized value
然后setting is Some(5)
.在第一个 match 分支中,我们不需要 match on 或使用
其中的Some
变体,但我们确实需要测试该情况
什么时候setting_value
和new_setting_value
是Some
变体。在那个
case 时,我们打印不更改的原因setting_value
,并且它不会得到
改变。
在所有其他情况下(如果setting_value
或new_setting_value
是None
) 表示的 Pattern 表示的_
new_setting_value
成为setting_value
.
我们也可以在一个模式中的多个位置使用下划线来忽略 特定值。示例 18-19 显示了一个忽略第二个 和 fourth 个值。
fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (first, _, third, _, fifth) => { println!("Some numbers: {first}, {third}, {fifth}") } } }
示例 18-19:忽略元组的多个部分
此代码将打印Some numbers: 2, 8, 32
,值 4 和 16 将为
忽视。
忽略未使用的变量,使其名称以_
如果你创建了一个变量但不在任何地方使用它,Rust 通常会发出一个 警告,因为未使用的变量可能是 bug。然而,有时它是 对于能够创建尚不会使用的变量非常有用,例如当您 原型设计或刚刚开始一个项目。在这种情况下,你可以告诉 Rust 不通过启动变量名称来警告您未使用的变量 带有下划线。在示例 18-20 中,我们创建了两个未使用的变量,但是当 我们编译这段代码,我们应该只收到关于其中一个的警告。
文件名: src/main.rs
fn main() { let _x = 5; let y = 10; }
示例 18-20:以 下划线以避免收到未使用的变量警告
在这里,我们收到一条关于不使用变量的警告y
,但是我们没有得到
关于不使用的警告_x
.
请注意,仅使用和使用 name 之间存在细微差别
这以下划线开头。语法_
_x
still 将值绑定到
variable,而根本不绑定。要显示此
区别很重要,示例 18-21 会给我们一个错误。_
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{s:?}");
}
示例 18-21:以 下划线仍然绑定值,该值可能会获得值的所有权
我们会收到一个错误,因为s
值仍将移动到_s
,
这阻止了我们使用s
再。但是,单独使用下划线
永远不会绑定到 value。示例 18-22 将编译时没有任何错误
因为s
不会移动到 中。_
fn main() { let s = Some(String::from("Hello!")); if let Some(_) = s { println!("found a string"); } println!("{s:?}"); }
示例 18-22:使用下划线不会绑定 价值
这段代码工作得很好,因为我们从不绑定s
对任何东西;它不会移动。
忽略 Value 的剩余部分..
对于包含许多部分的值,我们可以使用..
语法以使用特定的
部分并忽略其余部分,从而避免为每个部分列出下划线的需要
ignored 值。这..
pattern 会忽略值中我们尚未忽略的部分
在 pattern 的其余部分显式匹配。在示例 18-23 中,我们有一个Point
在三维空间中保存坐标的结构。在match
表达式,我们只想对x
协调和忽略
的y
和z
领域。
fn main() { struct Point { x: i32, y: i32, z: i32, } let origin = Point { x: 0, y: 0, z: 0 }; match origin { Point { x, .. } => println!("x is {x}"), } }
示例 18-23:忽略Point
除了
为x
通过使用..
我们列出了x
值,然后只包含..
模式。这样更快
而不是必须列出y: _
和z: _
,尤其是当我们使用
在只有一个或两个字段的情况下具有大量字段的结构体
相关。
语法..
将扩展为所需数量的值。示例 18-24
演示如何使用..
替换为元组。
文件名: src/main.rs
fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (first, .., last) => { println!("Some numbers: {first}, {last}"); } } }
示例 18-24:仅匹配 中的第一个和最后一个值 一个 Tuples 并忽略所有其他值
在此代码中,第一个和最后一个值与first
和last
.这..
将匹配并忽略中间的所有内容。
但是,使用..
必须明确。如果不清楚哪些值是
用于匹配,应该忽略它,Rust 会给我们一个错误。
示例 18-25 显示了使用..
模棱两可,所以它不会
编译。
文件名: src/main.rs
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {second}")
},
}
}
示例 18-25:尝试使用..
在一个模棱两可的
道路
当我们编译此示例时,我们会收到以下错误:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error: `..` can only be used once per tuple pattern
--> src/main.rs:5:22
|
5 | (.., second, ..) => {
| -- ^^ can only be used once per tuple pattern
| |
| previously used here
error: could not compile `patterns` (bin "patterns") due to 1 previous error
Rust 无法确定要忽略元组中的多少个值
在将值与second
然后是
此后忽略。这段代码可能意味着我们想忽略2
捆second
自4
,然后忽略8
,16
和32
;或者我们想忽略的2
和4
捆second
自8
,然后忽略16
和32
;等等。
变量名称second
对 Rust 来说没有什么特别的意义,所以我们得到了一个
编译器错误,因为使用..
在两个地方,像这样是模棱两可的。
带有 Match Guard 的额外条件语句
火柴守卫是额外的if
condition,在
一个match
arm,该 arm也必须与要选择的 arm 匹配。火柴守卫是
对于表达比单独的模式所允许的更复杂的想法很有用。
条件可以使用在模式中创建的变量。示例 18-26 显示了一个match
其中第一个臂具有 patternSome(x)
并且还有一个匹配项
守卫if x % 2 == 0
(如果数字为偶数,则为 true)。
fn main() { let num = Some(4); match num { Some(x) if x % 2 == 0 => println!("The number {x} is even"), Some(x) => println!("The number {x} is odd"), None => (), } }
示例 18-26:向模式添加 match 守卫
此示例将打印The number 4 is even
.什么时候num
与
pattern 中,它会匹配,因为Some(4)
比赛Some(x)
.然后
match 守卫检查 divideing 的余数是否x
by 2 等于
0,因为它是,所以会选择第一个分支。
如果num
一直Some(5)
相反,第一臂中的火柴后卫会
都是 false,因为 5 除以 2 的余数是 1,而 1 不是
等于 0。然后 Rust 将转到第二个分支,这将匹配,因为
第二个臂没有匹配守卫,因此匹配任何Some
变体。
没有办法表示if x % 2 == 0
condition 的 Pattern 中,因此
match 守卫让我们能够表达这个逻辑。缺点
这种额外的表达性是编译器不会尝试检查
涉及 match guard 表达式时的穷举性。
在示例 18-11 中,我们提到我们可以使用 match 守卫来解决
pattern-shadowing 问题。回想一下,我们在
pattern 中match
expression 而不是使用match
.这个新变量意味着我们无法根据
outer 变量。示例 18-27 展示了如何使用 match 守卫来解决这个问题
问题。
文件名: src/main.rs
fn main() { let x = Some(5); let y = 10; match x { Some(50) => println!("Got 50"), Some(n) if n == y => println!("Matched, n = {n}"), _ => println!("Default case, x = {x:?}"), } println!("at the end: x = {x:?}, y = {y}"); }
示例 18-27:使用 match 守卫测试相等性 替换为外部变量
此代码现在将打印Default case, x = Some(5)
.第二个
match arm 不会引入新变量y
那会遮蔽外层y
,
这意味着我们可以使用 externaly
在火柴后卫中。而不是指定
pattern 设置为Some(y)
,这会遮蔽外部y
,我们指定Some(n)
.这将创建一个新变量n
那不会影子,因为
没有n
变量match
.
火柴后卫if n == y
不是模式,因此不会引入
新变量。这y
是外部的y
而不是新的阴影y
和
我们可以寻找一个与 outer 具有相同值的值y
通过比较n
自y
.
您还可以使用 or 运算符|
在 match 守卫中指定多个
模式;match guard 条件将应用于所有模式。清单
18-28 显示了组合使用|
带火柴
警卫。此示例的重要部分是if y
比赛守卫
适用于4
,5
和 6
,即使它可能看起来像if y
只
适用于6
.
fn main() { let x = 4; let y = false; match x { 4 | 5 | 6 if y => println!("yes"), _ => println!("no"), } }
示例 18-28:将多个模式与匹配项组合在一起 警卫
匹配条件声明,仅当x
是
等于4
,5
或6
如果y
是true
.当此代码运行时,
pattern 匹配,因为x
是4
,但 match 后卫if y
为 false,因此不会选择第一个分支。代码移动到第二个分支
匹配,并且此程序会打印no
.原因是if
条件应用于整个模式4 | 5 | 6
,不仅到最后一个值6
.换句话说,match 守卫相对于 pattern 的优先级
的行为如下:
(4 | 5 | 6) if y => ...
而不是这样:
4 | 5 | (6 if y) => ...
运行代码后,优先行为很明显:如果 match 守卫
仅应用于使用|
运算符,则 arm 将匹配,并且程序将打印yes
.
@
绑定
at 运算符让我们创建一个变量,该变量保存一个值
time 进行验证,因为我们要测试 pattern match 的该值。在示例 18-29 中,我们希望
以测试@
Message::Hello
id
字段在范围3..=7
.我们还
想要将值绑定到变量id_variable
因此,我们可以在
与 ARM 关联的代码。我们可以将这个变量命名为id
,与
field 中,但在此示例中,我们将使用不同的名称。
fn main() { 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}"), } }
示例 18-29:用于绑定到模式中的值
同时也对其进行了测试@
此示例将打印Found an id in range: 5
.通过指定id_variable @
在范围之前3..=7
,我们将捕获与范围匹配的任何值
同时还测试该值是否与 Range 模式匹配。
在第二个分支中,我们只在 pattern 中指定了范围,代码
关联 ARM 没有包含实际值的变量
的id
田。这id
field 的值可以是 10、11 或 12,但
与该模式配套的代码不知道它是哪个模式。模式代码
无法使用id
字段中,因为我们还没有保存id
值。
在最后一个分支中,我们指定了一个没有范围的变量,我们确实有
可在 Arm 代码中名为id
.这
原因是我们使用了 struct field 速记语法。但我们没有
将任何测试应用于id
字段,就像我们对
First Two Arms:任何值都将匹配此模式。
Using 让我们测试一个值并将其保存在一个模式中的变量中。@
总结
Rust 的模式在区分不同种类的
数据。当用于match
表达式,Rust 会确保你的模式覆盖每个
可能的值,否则您的程序将无法编译。模式let
statements 和
函数参数使这些结构更有用,从而启用
将值解构为更小的部分,同时赋值给
变量。我们可以创建简单或复杂的模式来满足我们的需要。
接下来,在本书的倒数第二章,我们将看看一些高级 Rust 的各种功能的各个方面。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准