模式语法
在本节中,我们收集了模式中所有有效的语法,并讨论了原因和 何时您可能想要使用每一个。
匹配文本
正如你在第 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.然后,我们创建一个matchvalue 上的 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 开头。这个新的ybinding 将匹配任何值
在Some,这就是我们在x.因此,这个新的y绑定到
的Some在x.该值为5,因此
该 arm 执行并打印Matched, y = 5.
如果x曾是Nonevalue 而不是Some(5)中,第一个
两个 Arms 不匹配,因此该值将与
强调。我们没有引入x变量在
underscore arm,因此x在表达式中仍然是外部的x那没有
被遮蔽。在这个假设的情况下,match将打印Default case, x = None.
当matchexpression 完成,则其范围结束,则
内部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 显示了一个Pointstruct 中具有两个字段,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 中的代码一样,但是在letpattern 为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值
分为三种情况:直接位于xaxis (当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轴,方法是指定
这xfield 如果其值为0并创建一个变量y对于
的值y田。第三个分支没有指定任何文本,因此它
匹配任何其他Point并为x和y领域。
在此示例中,值p凭借x包含 0,因此此代码将打印On the y axis at 7.
请记住,一个match表达式在找到
第一个匹配模式,因此即使Point { x: 0, y: 0}位于x轴
和yaxis 的 Axis 中,此代码只会打印On the x axis at 0.
解构枚举
我们在本书中解构了枚举(例如,第 6 章中的示例 6-5),
但尚未明确讨论解构枚举的模式
对应于定义存储在枚举中的数据的方式。作为
例如,在示例 18-15 中,我们使用了Messageenum 并写入
一个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 中的代码在ChangeColormessage 中,如示例 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::ChangeColorenum 变体,其中包含Color::Rgb变体;然后
模式绑定到三个内部i32值。第二个
arm 还会匹配Message::ChangeColorenum 变体,但内部枚举
比赛Color::Hsv相反。我们可以在一个matchexpression,即使涉及两个枚举。
解构结构和元组
我们可以以更复杂的方式混合、匹配和嵌套解构模式。 以下示例显示了一个复杂的解构,其中我们将结构体和 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 之间存在细微差别
这以下划线开头。语法__xstill 将值绑定到
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 的额外条件语句
火柴守卫是额外的ifcondition,在
一个matcharm,该 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 的余数是否xby 2 等于
0,因为它是,所以会选择第一个分支。
如果num一直Some(5)相反,第一臂中的火柴后卫会
都是 false,因为 5 除以 2 的余数是 1,而 1 不是
等于 0。然后 Rust 将转到第二个分支,这将匹配,因为
第二个臂没有匹配守卫,因此匹配任何Some变体。
没有办法表示if x % 2 == 0condition 的 Pattern 中,因此
match 守卫让我们能够表达这个逻辑。缺点
这种额外的表达性是编译器不会尝试检查
涉及 match guard 表达式时的穷举性。
在示例 18-11 中,我们提到我们可以使用 match 守卫来解决
pattern-shadowing 问题。回想一下,我们在
pattern 中matchexpression 而不是使用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田。这idfield 的值可以是 10、11 或 12,但
与该模式配套的代码不知道它是哪个模式。模式代码
无法使用id字段中,因为我们还没有保存id值。
在最后一个分支中,我们指定了一个没有范围的变量,我们确实有
可在 Arm 代码中名为id.这
原因是我们使用了 struct field 速记语法。但我们没有
将任何测试应用于id字段,就像我们对
First Two Arms:任何值都将匹配此模式。
Using 让我们测试一个值并将其保存在一个模式中的变量中。@
总结
Rust 的模式在区分不同种类的
数据。当用于match表达式,Rust 会确保你的模式覆盖每个
可能的值,否则您的程序将无法编译。模式letstatements 和
函数参数使这些结构更有用,从而启用
将值解构为更小的部分,同时赋值给
变量。我们可以创建简单或复杂的模式来满足我们的需要。
接下来,在本书的倒数第二章,我们将看看一些高级 Rust 的各种功能的各个方面。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准