当前位置: 首页 > news >正文

【Rust模式与匹配】Rust模式与匹配深入探索与应用实战

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Rust语言通关之路
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • Rust模式和匹配
    • 一、 模式匹配基础
      • 1.1 什么是模式匹配
      • 1.2 基本match表达式
      • 1.3 模式匹配的穷尽性检查
      • 1.4 let 语句
      • 1.5 函数参数
    • 二、Refutability(可反驳性): 模式是否会匹配失效
    • 三、模式匹配的各种形式
      • 3.1 字面量匹配
      • 3.2 匹配命名变量
      • 3.3 匹配多个模式
      • 3.4 变量绑定
      • 3.5 解构匹配
      • 3.6 引用匹配
      • 3.7 模式守卫
      • 3.8 if let 、while let和for循环
        • 3.8.1 if let 简洁匹配
        • 3.8.2 while let 条件循环
        • 3.8.3 for循环
    • 四、高级模式匹配技巧
      • 4.1 忽略模式中的值
        • 4.1.1 忽略模式部分
        • 4.1.2 使用 _ 忽略整个值
      • 4.2 匹配守卫与绑定
    • 五、总结

Rust模式和匹配

模式是 Rust 中特殊的语法,它用来匹配类型中的结构,无论类型是简单还是复杂。
结合使用模式和 match 表达式以及其他结构可以提供更多对程序控制流的支配权。模式由如下一些内容组合而成:
字面量
解构的数组、枚举、结构体或者元组
变量
通配符
占位符
这些部分描述了我们要处理的数据的形状,接着可以用其匹配值来决定程序是否拥有正确的数据来运行特定部分的代码。
我们通过将一些值与模式相比较来使用它。如果模式匹配这些值,我们对值部分进行相应处理。

本文是所有模式相关内容的参考。我们将涉及到使用模式的有效位置,refutable 与 irrefutable 模式的区别,和你可能会见到的不同类型的模式语法。
在最后,你将会看到如何使用模式创建强大而简洁的代码。

一、 模式匹配基础

1.1 什么是模式匹配

模式匹配是Rust语言中一种强大的控制流结构,它允许你根据数据的形状来检查数据并相应地执行代码。
与传统的if-else语句不同,模式匹配提供了更清晰、更安全的方式来处理多种可能的数据状态。

在Rust中,模式匹配主要通过match表达式和if let/while let结构来实现。这些结构不仅简洁明了,还能在编译时检查所有可能的情况,避免遗漏处理某些情形。

1.2 基本match表达式

最基本的模式匹配形式是match表达式,它类似于其他语言中的switch语句,但功能更强大。

let number = 13;match number {1 => println!("One"),2 | 3 | 5 | 7 | 11 => println!("This is a prime"),13..=19 => println!("A teen"),_ => println!("Ain't special"),
}

在这个例子中:
1匹配精确值1
2 | 3 | 5 | 7 | 11使用|表示"或"关系 属于多个模式匹配
13…=19匹配13到19的闭区间范围 通过 …= 匹配值的范围 …= 语法允许你匹配一个闭区间范围内的值。
_是通配符模式,匹配任何值 我们已经使用过下划线(_)作为匹配但不绑定任何值的通配符模式了。

1.3 模式匹配的穷尽性检查

Rust编译器强制要求match表达式必须覆盖所有可能的情况。这种穷尽性检查是Rust安全保证的重要组成部分。

#[allow(dead_code)]
enum Direction {North,South,East,West,
}fn main() {let dir = Direction::North;match dir {Direction::North => println!("Heading north"),Direction::South => println!("Heading south"),Direction::East => println!("Heading east"),Direction::West => println!("Heading west"),// 如果注释掉任何一个分支,编译器会报错}
}

在这里插入图片描述

1.4 let 语句

let语句的模式是:let PATTERN = EXPRESSION;
像 let x = 5; 这样的语句中变量名位于 PATTERN 位置,变量名不过是形式特别朴素的模式。
我们将表达式与模式比较,并为任何找到的名称赋值。所以例如 let x = 5; 的情况,x 是一个表示“将匹配到的值绑定到变量 x” 的模式。
同时因为名称 x 是整个模式,这个模式实际上等于 “将任何值绑定到变量 x,不管值是什么”。
为了更清楚的理解 let 的模式匹配方面的内容,使用 let 和模式解构一个元组:
let (x, y, z) = (1, 2, 3);

这里将一个元组与模式匹配。Rust 会比较值 (1, 2, 3) 与模式 (x, y, z) 并发现此值匹配这个模式。
在这个例子中,将会把 1 绑定到 x,2 绑定到 y 并将 3 绑定到 z。你可以将这个元组模式看作是将三个独立的变量模式结合在一起。

如果模式中元素的数量不匹配元组中元素的数量,则整个类型不匹配,并会得到一个编译时错误。
例如,如下示例展示了尝试用两个变量解构三个元素的元组,这是不行的:
let (x, y) = (1, 2, 3);
在这里插入图片描述

1.5 函数参数

函数参数也可以是模式。

fn foo(x: i32) {// 代码
}
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。

因为如闭包类似于函数,也可以在闭包参数列表中使用模式。
现在我们见过了很多使用模式的方式了,不过模式在每个使用它的地方并不以相同的方式工作;在一些地方,模式必须是 irrefutable 的,意味着他们必须匹配所提供的任何值。
在另一些情况,他们则可以是 refutable 的。接下来让我们讨论这两个概念。

二、Refutability(可反驳性): 模式是否会匹配失效

模式有两种形式:refutable(可反驳的)和 irrefutable(不可反驳的)。
能匹配任何传递的可能值的模式被称为是 不可反驳的(irrefutable)。
一个例子就是 let x = 5; 语句中的 x,因为 x 可以匹配任何值所以不可能会失败。
对某些可能的值进行匹配会失败的模式被称为是 可反驳的(refutable)。
一个这样的例子便是 if let Some(x) = a_value 表达式中的 Some(x);如果变量 a_value 中的值是 None 而不是 Some,那么 Some(x) 模式不能匹配。

函数参数、 let 语句和 for 循环只能接受不可反驳的模式,因为通过不匹配的值程序无法进行有意义的工作。
if let 和 while let 表达式被限制为只能接受可反驳的模式,因为根据定义他们意在处理可能的失败:条件表达式的功能就是根据成功或失败执行不同的操作。
通常我们无需担心可反驳和不可反驳模式的区别,不过确实需要熟悉可反驳性的概念,这样当在错误信息中看到时就知道如何应对。
遇到这些情况,根据代码行为的意图,需要修改模式或者使用模式的结构。

match 匹配分支必须使用可反驳模式,除了最后一个分支需要使用能匹配任何剩余值的不可反驳模式。
Rust 允许我们在只有一个匹配分支的 match 中使用不可反驳模式,不过这么做不是特别有用,并可以被更简单的 let 语句替代。

//模式匹配中的可反驳模式和不可反驳模式fn main() {//let语句、函数参数和for循环中的模式必须是不可反驳的let x = 5; //不可反驳模式let y: Option<i32> = Some(5); //不可反驳模式//可反驳模式不能用let来定义// let Some(z) = y; //可反驳模式//可以使用if let或者while let来处理可反驳模式if let Some(z) = y {println!("z: {}", z);}//如果if let用于处理不可反驳模式,编译器会警告if let x = 5 {println!("x: {}", x);}
}

在这里插入图片描述

三、模式匹配的各种形式

3.1 字面量匹配

最简单的模式是直接匹配字面量值:

let x = 1;match x {1 => println!("one"),2 => println!("two"),3 => println!("three"),_ => println!("anything"),
}

3.2 匹配命名变量

名变量是匹配任何值的不可反驳模式,这在之前已经使用过数次。
然而当其用于 match 表达式时情况会有些复杂。因为 match 会开始一个新作用域,match 表达式中作为模式的一部分声明的变量会覆盖 match 结构之外的同名变量,与所有变量一样。

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 = {:?}, y = {:?}", x, y);
}

让我们看看当 match 语句运行的时候发生了什么。第一个匹配分支的模式并不匹配 x 中定义的值,所以代码继续执行。
第二个匹配分支中的模式引入了一个新变量 y,它会匹配任何 Some 中的值。
因为我们在 match 表达式的新作用域中,这是一个新变量,而不是开头声明为值 10 的那个 y。
这个新的 y 绑定会匹配任何 Some 中的值,在这里是 x 中的值。
因此这个 y 绑定了 x 中 Some 内部的值。这个值是 5,所以这个分支的表达式将会执行并打印出 Matched, y = 5。

如果 x 的值是 None 而不是 Some(5),头两个分支的模式不会匹配,所以会匹配下划线。
这个分支的模式中没有引入变量 x,所以此时表达式中的 x 会是外部没有被覆盖的 x。在这个假想的例子中,match 将会打印 Default case, x = None。

一旦 match 表达式执行完毕,其作用域也就结束了,同理内部 y 的作用域也结束了。最后的 println! 会打印 at the end: x = Some(5), y = 10。

为了创建能够比较外部 x 和 y 的值,而不引入覆盖变量的 match 表达式,我们需要相应地使用带有条件的匹配守卫(match guard)。
我们稍后将在 “匹配守卫提供的额外条件” 这一小节讨论匹配守卫。
在这里插入图片描述

3.3 匹配多个模式

在 match 表达式中,可以使用 | 语法匹配多个模式,它代表 或(or)的意思。
例如,如下代码将 x 的值与匹配分支相比较,第一个分支有 或 选项,意味着如果 x 的值匹配此分支的任一个值,它就会运行:

fn main() {let x = 1;match x {1 | 2 => println!("one or two"),3 => println!("three"),_ => println!("anything"),}
}

在这里插入图片描述

3.4 变量绑定

@ 绑定
at 运算符(@)允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。
如下示例展示了一个例子,这里我们希望测试 Message::Hello 的 id 字段是否位于 3…=7 范围内,同时也希望能将其值绑定到 id_variable 变量中以便此分支相关联的代码可以使用它。
可以将 id_variable 命名为 id,与字段同名,不过出于示例的目的这里选择了不同的名称。
模式可以绑定变量,这使得我们可以在匹配的同时提取值:

fn main() {enum Message {Hello {id: i32,},}let msg = Message::Hello { id: 5 };match msg {//将id绑定到变量id_variable上//@指定匹配的范围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) }}
}

在这里插入图片描述

3.5 解构匹配

模式匹配最强大的功能之一是能够解构复杂的数据类型:
解构元组

fn main() {let pair = (0, -2);//解构元组match pair {(0, y) => println!("First is 0, y is {}", y),(x, 0) => println!("x is {}, second is 0", x),_ => println!("No zero"),}
}

在这里插入图片描述

解构结构体

fn main() {struct Point {x: i32,y: i32,}let point = Point { x: 0, y: 7 };match point {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),}
}

在这里插入图片描述

解构枚举

#[allow(dead_code)]
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!("Quit"),Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y),Message::Write(text) => println!("Text message: {}", text),Message::ChangeColor(r, g, b) => println!("Change color to RGB: {}, {}, {}", r, g, b),}
}

在这里插入图片描述

解构嵌套的结构体和枚举

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));//其他的我们都不关心,我们只匹配ChangeColormatch msg {Message::ChangeColor(Color::Rgb(r, g, b)) => {println!("rgb red {}, green {}, and blue {}", r, g, b)}Message::ChangeColor(Color::Hsv(h, s, v)) => {println!("hsv to hue {}, saturation {}, and value {}", h, s, v)}_ => (),}
}

在这里插入图片描述

3.6 引用匹配

当匹配引用时,需要使用&来解构:

fn main() {let reference = &4;match reference {&val => println!("Got a value via destructuring: {}", val),}// 更简单的方式是使用解引用match *reference {val => println!("Got a value via dereferencing: {}", val),}
}

在这里插入图片描述

3.7 模式守卫

匹配守卫(match guard)是一个指定于 match 分支模式之后的额外 if 条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。
模式守卫允许在模式匹配中添加额外的条件:

fn main() {let pair = (2, -2);match pair {//匹配后面跟if条件判断(x, y) if x == y => println!("These are twins"),(x, y) if x + y == 0 => println!("Antimatter, kaboom!"),(x, _) if x % 2 == 1 => println!("The first one is odd"),_ => println!("No correlation"),}
}

在这里插入图片描述

3.8 if let 、while let和for循环

3.8.1 if let 简洁匹配
if let提供了一种更简洁的方式来处理只关心一种模式而忽略其他模式的情况:
fn main() {let some_value = Some(3);// 使用matchmatch some_value {Some(3) => println!("three"),_ => (),}// 使用if let更简洁if let Some(3) = some_value {println!("three");}
}

在这里插入图片描述

3.8.2 while let 条件循环

while let使得只要模式匹配就一直循环:

fn main() {let mut stack = Vec::new();stack.push(1);stack.push(2);stack.push(3);//使用while let来处理Option类型//只要stack.pop()返回Some,就执行循环体while let Some(top) = stack.pop() {println!("{}", top);}
}

在这里插入图片描述

3.8.3 for循环

for 循环是 Rust 中最常见的循环结构,不过还没有讲到的是 for 可以获取一个模式。
在 for 循环中,模式是 for 关键字直接跟随的值,正如 for x in y 中的 x。

fn main() {let v = vec!['a', 'b', 'c'];//通过for循环来遍历vector生成的迭代器for (index, value) in v.iter().enumerate() {println!("{} is at index {}", value, index);}
}

这里使用 enumerate 方法适配一个迭代器来产生一个值和其在迭代器中的索引,他们位于一个元组中。
第一个 enumerate 调用会产生元组 (0, ‘a’)。当这个值匹配模式 (index, value),index 将会是 0 而 value 将会是 ‘a’,并打印出第一行输出。以此类推。
在这里插入图片描述

四、高级模式匹配技巧

4.1 忽略模式中的值

4.1.1 忽略模式部分

对于有多个部分的值,可以使用 … 语法来只使用部分并忽略其它值,同时避免不得不每一个忽略值列出下划线。… 模式会忽略模式中剩余的任何没有显式匹配的值部分。
可以使用_或…来忽略值的部分:

#[allow(dead_code)]
struct Point {x: i32,y: i32,z: i32,
}fn main() {let origin = Point { x: 0, y: 0, z: 0 };match origin {//..表示忽略其他字段Point { x, .. } => println!("x is {}", x),}let numbers = (2, 4, 8, 16, 32);match numbers {//只需要第一个和最后一个值,其他的忽略(first, .., last) => {println!("Some numbers: {}, {}", first, last);}}
}

在这里插入图片描述

注意:使用 … 必须是无歧义的。如果期望匹配和忽略的值是不明确的,Rust 会报错。
如下示例展示了一个带有歧义的 … 例子,因此其不能编译:

fn main() {let numbers = (2, 4, 8, 16, 32);match numbers {(.., second, ..) => { println!("Some numbers: {}", second) }}
}

Rust 不可能决定在元组中匹配 second 值之前应该忽略多少个值,以及在之后忽略多少个值。
这段代码可能表明我们意在忽略 2,绑定 second 为 4,接着忽略 8、16 和 32;抑或是意在忽略 2 和 4,绑定 second 为 8,接着忽略 16 和 32,以此类推。
变量名 second 对于 Rust 来说并没有任何特殊意义,所以会得到编译错误,因为在这两个地方使用 … 是有歧义的。
在这里插入图片描述

… 在同一个匹配中只能使用一次

4.1.2 使用 _ 忽略整个值

我们已经使用过下划线(_)作为匹配但不绑定任何值的通配符模式了。
虽然 _ 模式作为 match 表达式最后的分支特别有用,也可以将其用于任意模式,包括函数参数中,

//定义一个函数,参数使用下划线来忽略
fn foo(_: i32, y: i32) {println!("This code only uses the y parameter: {}", y);
}fn main() {//调用函数的时候,第一个参数随便写,也可以使用下划线foo(3, 4);
}

在这里插入图片描述

大部分情况当你不再需要特定函数参数时,最好修改签名不再包含无用的参数。
在一些情况下忽略函数参数会变得特别有用,比如实现 trait 时,当你需要特定类型签名但是函数实现并不需要某个参数时。
此时编译器就不会警告说存在未使用的函数参数,就跟使用命名参数一样。
如下:
在这里插入图片描述

使用嵌套的 _ 忽略部分值
也可以在一个模式内部使用 _ 忽略部分值,例如,当只需要测试部分值但在期望运行的代码中没有用到其他部分时。
如下示例展示了负责管理设置值的代码。业务需求是用户不允许覆盖现有的自定义设置,但是可以取消设置,也可以在当前未设置时为其提供设置。

fn main() {let mut setting_value = Some(5);let new_setting_value = Some(10);match (setting_value, new_setting_value) {//使用Some(_),Some(_)来匹配Some(5)和Some(10)(Some(_), Some(_)) => {println!("Can't overwrite an existing customized value");}_ => {setting_value = new_setting_value;}}println!("setting is {:?}", setting_value);
}

这段代码会打印出 Can’t overwrite an existing customized value 接着是 setting is Some(5)。
在第一个匹配分支,我们不需要匹配或使用任一个 Some 成员中的值;重要的部分是需要测试 setting_value 和 new_setting_value 都为 Some 成员的情况。
在这种情况,我们打印出为何不改变 setting_value,并且不会改变它。
对于所有其他情况(setting_value 或 new_setting_value 任一为 None),这由第二个分支的 _ 模式体现,这时确实希望允许 new_setting_value 变为 setting_value。
在这里插入图片描述

通过在名字前以一个下划线开头来忽略未使用的变量
如果你创建了一个变量却不在任何地方使用它, Rust 通常会给你一个警告,因为这可能会是个 bug。
但是有时创建一个还未使用的变量是有用的,比如你正在设计原型或刚刚开始一个项目。
这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头。

fn main() {let _x = 5;let y = 10;
}

这里得到了警告说未使用变量 y,不过没有警告说未使用下划线开头的变量。

注意, 只使用 _ 和使用以下划线开头的名称有些微妙的不同:比如 _x 仍会将值绑定到变量,而 _ 则完全不会绑定。

fn main() {let s = Some(String::from("Hello!"));if let Some(_s) = s {println!("found a string");}println!("{:?}", s);  //报错,因为s的所有权已经转移给_s了
}

在这里插入图片描述

使用单独的下划线,变量不会绑定
在这里插入图片描述

4.2 匹配守卫与绑定

结合匹配守卫和绑定可以实现复杂的条件判断:

fn main() {let robot_name = Some(String::from("Bors"));match robot_name {//使用ref来获取Some的值的引用//避免了所有权的转移Some(ref name) if name == "Bors" => {println!("Found robot: {}", name);}Some(name) => {println!("Not the robot: {}", name);}None => {}}
}

在这里插入图片描述

五、总结

Rust的模式匹配系统是其最强大和最独特的特性之一。它提供了:

  • 清晰、表达力强的语法

  • 编译时的安全性检查

  • 高效的运行时性能

  • 对复杂数据结构的强大解构能力

通过掌握模式匹配,大家可以编写出更安全、更清晰、更易于维护的Rust代码。从简单的值匹配到复杂的解构和守卫,模式匹配几乎可以处理任何数据验证和提取需求。

Rust的模式匹配不仅仅是一个语言特性,它体现了Rust的核心哲学:在编译时捕获尽可能多的错误,同时保持运行时的效率。随着你对Rust的深入使用,你会发现模式匹配成为了你工具箱中不可或缺的一部分。

相关文章:

  • 力扣面试150题--二叉树的右视图
  • 高速连接器设计的真相
  • 由enctype-引出post与get的关系,最后深究至请求/响应报文
  • windows系统下通过visual studio使用clang tooling
  • 【HarmonyOS Next之旅】DevEco Studio使用指南(二十八) -> 开发云对象
  • 变更数据捕获(CDC)与流处理引擎实现医疗数据实时同步(下)
  • 【Python】3.函数与列表
  • 2025.05.28-华为暑期实习第二题-200分
  • Python 科学计算有哪些提高运算速度的技巧
  • 机器人--里程计
  • Java—多线程
  • DM达梦数据库开启SQL日志记录功能
  • DeepSeek 工作应用深度指南
  • xcode卡死问题,无论打开什么程序xcode总是在转菊花,重启电脑,卸载重装都不行
  • 【人工智能】DeepSeek的AI狂想曲:从训练到应用的交响乐
  • Lesson 9 防火墙 iptables 和 firewalld
  • 金山云Q1营收19.7亿元 AI持续释放业务增长新动能
  • 暗通道先验去雾算法实现
  • NW845NW850美光闪存颗粒NW883NW889
  • Linux云计算训练营笔记day18(Python)
  • 网站主机注册/大兴今日头条新闻
  • 网站被301跳转/山东网络优化公司排名
  • 诸城网站建设哪家好/如何免费制作网站
  • 网站界面用什么做的/优化步骤
  • 常德网站开发公司/肇庆疫情最新情况
  • 设计网站作品欣赏有那些网站/百度网盘网页版登录入口官网