【Rust自学】18.3. 模式(匹配)的语法

embedded/2025/2/6 11:30:15/

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

18.3.1. 匹配字面值

模式可以直接匹配字面值。看个例子:

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

此代码打印one ,因为x中的值为 1。当希望代码在获取特定具体值时执行操作时,此语法非常有用。

18.3.2. 匹配命名变量

命名的变量是可匹配任何值的无可辩驳模式。看个例子:

rust">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}");

这个例子的逻辑很简单,主要是看这段代码中出现两个y。这两个y没有任何关系,处于不同的作用域,let y = 10y就是为了存储10,而Some(y)y主要是提取Option类型下的Some变体里附带的数据。

match里的执行逻辑:

  • 第一个分支中的模式与x的定义值不匹配,因此代码继续。

  • 第二个匹配臂中的模式引入了一个名为y的新变量,它将匹配Some值内的任何值。因为我们处于match表达式内的新作用域,所以这是一个新的y变量,而不是我们在开头声明的值为10的y 。这个新y绑定将匹配Some内的任何值,这就是我们所拥有的在x中。因此,这个新的y绑定到xSome的内部值。该值为5 ,因此该臂的表达式将执行并打印Matched, y = 5

  • 如果xNone值而不是Some(5) (当然这个例子里不可能),则前两个臂中的模式将不匹配,因此该值将与下划线匹配。我们没有在下划线臂的模式中引入x变量,因此表达式中的x仍然是没有被遮蔽的外部x 。在这个假设的情况下, match将打印Default case, x = None

输出:

Matched, y = 5
at the end: x = Some(5), y = 10

18.3.3. 多重模式

match表达式里,使用管道符|语法(就是的意思),可以匹配多种模式。看个例子:

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

例子中的第一个分支就是x为1或2都能匹配。

18.3.4. 使用..=来匹配某个范围的值

看例子:

rust">let x = 5;match x {1..=5 => println!("one through five"),_ => println!("something else"),
}

这个例子的第一个分支表示当x值为1到5(闭区间),也就是1、2、3、4、5的任意一个时都会匹配。

由于 Rust 可以判断范围是否为空的唯一类型是char和数值,因此范围仅允许包含数字或char值。看个例子:

rust">let x = 'c';match x {'a'..='j' => println!("early ASCII letter"),'k'..='z' => println!("late ASCII letter"),_ => println!("something else"),
}

这个例子的第一个分支代表匹配从aj的字符,第二个分支代表匹配从kz到字符。

18.3.5. 解构以分解值

我们可以使用模式来解构structenumtuple,从而引用这些类型值的不同部分。

解构struct

看个例子:

rust">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);
}
  • Point结构体下有两个字段xy,都是i32类型
  • 有一个Point的实例叫p,其x字段值为0,y字段值为7
  • 然后使用模式对p进行解构,x的值被赋给ay的值被赋给了b

这么写还是有些冗杂,如果把a的变量名变为xb的变量名变为y,就可以简写为:

rust">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);
}

解构还可以灵活地使用。看个例子:

rust">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})");}}
}
  • 第一个分支要求x字段值随意,y字段值为0
  • 第二个分支要求x字段值为0,y字段值随意
  • 第三个分支对xy的值无要求

解构enum

看例子:

rust">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

解构嵌套的structenum

看例子:

rust">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}")}_ => (),}
}

Message下的ChangeColor变体附带的数据就是Color枚举类型。使用match表达式匹配时一层一层匹配好即可。match的前两个分支外面都是ChangeColor变体,里面分别对应Color的两个变体,里面存的值都可以通过变量取出来。

解构structtuple

看例子:

rust">struct Point {x: i32,y: i32,
}fn main(){let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });	
}

main函数里的模式匹配外层是一个元组,这个元组有两个元素:

  • 第一个元素是个元组,里面有两个元素
  • 第二个是Point结构体

在模式中忽略值

有几种方式可以在模式中忽略整个值或部分值:

  • _:忽略整个值
  • _配合其他模式:忽略部分值
  • 使用以_开头的名称
  • ..:忽略值的剩余部分

使用_来忽略整个值

看例子:

rust">fn foo(_: i32, y: i32) {println!("This code only uses the y parameter: {y}");
}fn main() {foo(3, 4);
}

这个代码将完全忽略作为第一个参数传递的值3 ,并打印This code only uses the y parameter: 4

使用嵌套的_来忽略值的一部分

看例子:

rust">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:?}");

该代码将打印 Can't overwrite an existing customized value 进而 setting is Some(5) 。在第一个分支中,我们不需要匹配或使用Some变体中的值,但我们确实需要确定setting_valuenew_setting_valueSome变体。这就是忽略值的一部分。

第二个分支表示在所有其他情况下(如果setting_valuenew_setting_valueNone ),把new_setting_value变为setting_value 。这就是_配合其他模式来忽略某个值。

我们还可以在一种模式中的多个位置使用下划线来忽略特定值。看例子:

rust">let numbers = (2, 4, 8, 16, 32);match numbers {(first, _, third, _, fifth) => {println!("Some numbers: {first}, {third}, {fifth}")}
}

这里就忽略了元组的第2个和第4个元素。此代码将打印Some numbers: 2, 8, 32 ,并且值4和16将被忽略。

使用_开头命名来忽略未使用的变量

看例子:

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

正常情况下如果你创建了变量但没有使用它Rust编译器会发出警告,_xy就没被使用,但使用y的警告。因为_x使用_开头告诉编译器这是个临时的变量。

请注意,仅使用_和使用以下划线开头的名称之间存在细微差别。语法_x仍然将值绑定到变量,而_根本不绑定。看例子:

rust">let s = Some(String::from("Hello!"));if let Some(_s) = s {println!("found a string");
}println!("{s:?}");

我们会收到一个错误,因为s值仍会被移动到_s中,这会阻止我们打印s

对于这种情况,就应该使用_来避免绑定值的操作:

rust">let s = Some(String::from("Hello!"));if let Some(_) = s {println!("found a string");
}println!("{s:?}");

使用..来忽略值的剩余部分

看例子:

rust">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}"),}
}

使用match匹配时之需要x字段就可以了,所以模式匹配只写x,其余用..

这么使用..也是可以的:

rust">fn main() {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}")},}
}

前面是..,后面是..,我要中间的元素。但具体是哪个元素呢?这么写编译器不知道..具体要省略多少个元素,也就不明白second对应的是哪个元素。

输出:

$ cargo runCompiling 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 hereerror: could not compile `patterns` (bin "patterns") due to 1 previous error

18.3.6. 使用match guards(match守卫)来提供额外的条件

match guardsmatch分支模式后一个附加的if条件,想要匹配该分支该条件也必须能满足。match guards适用于比单独的模式更复杂的场景。

看例子:

rust">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 => (),}
}

match的第一个分支中,Some(x)是模式,而if x % 2 == 0就是match guards,要求Some附带的数据要能被2整除。

无法在模式中表达if x % 2 == 0条件,因此match guards使我们能够表达此逻辑。这种额外表达能力的缺点是,当涉及匹配保护表达式时,编译器不会尝试检查是否详尽。

看第二个例子:

rust">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}");
}

此代码现在将打印Default case, x = Some(5)

match守卫if n == y不是模式,因此不会引入新变量。这个y是外部y(值为10)而不是新的阴影y ,我们可以通过比较来查找与外部y具有相同值的值 ny

看第三个例子:

rust">    let x = 4;let y = false;match x {4 | 5 | 6 if y => println!("yes"),_ => println!("no"),}

这个例子配合多重模式来使用match守卫。

匹配条件规定,仅当x的值等于456ytrue时,该分支才匹配。当此代码运行时,因为x4 ,但匹配守卫yfalse,所以不会执行第一个分支而会执行第二个分支输出no

这个例子里需要注意的是匹配模式相对于match守卫的优先级,其优先级应该是:

rust">(4 | 5 | 6) if y => ...

而不是:

rust">4 | 5 | (6 if y) => ...

18.3.7. @绑定

@符号让我们可以创建一个变量,该变量可以在测试某个值是否与模式匹配的同时保存该值。

看例子:

rust">enum Message {Hello { id: i32 },
}fn main(){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}"),}
}

这个例子match的第一个分支在模式匹配时既把id字段的值绑定在id_varible上又判断了其值应该在3到7的闭区间内。


http://www.ppmy.cn/embedded/160010.html

相关文章

在Mapbox GL JS中“line-pattern”的使用详解

在Mapbox GL JS中,line-pattern 是一种用于在地图上绘制带有图案的线条的样式属性。通过 line-pattern,你可以使用自定义的图像作为线条的图案,而不是使用纯色或渐变。 1. 基本概念 line-pattern: 该属性允许你指定一个图像作为线条的图案。…

WordPress自定义.js文件排序实现方法

在WordPress中,要将插件引用的.js文件放到所有.js文件之后加载,可以通过以下方法实现: 方法一:调整wp_enqueue_script的加载顺序 在插件的主文件中,使用wp_enqueue_script函数加载.js文件时,将$in_footer…

Spring Boot 2 快速教程:WebFlux 集成 Mongodb(三)

一、前言 上一讲用 Map 数据结构内存式存储了数据。这样数据就不会持久化,本文我们用 MongoDB 来实现 WebFlux 对数据源的操作。 什么是 MongoDB ? 官网:https://www.mongodb.com/ MongoDB 是一个基于分布式文件存储的数据库,由 C 语言编…

《redis4.0 通信模块源码分析(一)》

【redis导读】redis作为一款高性能的内存数据库,面试服务端开发,redis是绕不开的话题,如果想提升自己的网络编程的水平和技巧,redis这款优秀的开源软件是很值得大家去分析和研究的。 笔者从大学毕业一直有分析redis源码的想法&…

vue2+vue3 HMCXY基础入门

vue2vue3 HMCXY基础入门 一、Vue2.x技术精讲1.Vue快速上手(1)Vue概念(2)创建实例(3)插值表达式(4)响应式特性(5)开发者工具 2.Vue指令二、Vue3.x技术精讲 一、…

【JavaEE】Spring(6):Mybatis(下)

一、Mybatis XML配置文件 Mybatis开发有两种方式: 注解XML 之前讲解了注解的方式,接下来学习XML的方式 1.1 配置数据库连接和Mybatis 直接在配置文件中配置即可: spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mybatis_test?cha…

基于单片机的智能感控杆设计(论文+源码)

2.1功能设计 本次以智能感控杆设计为题,智能感控杆是一种可以应用在多种场合的设备,可以极大的节约人类的精力和时间。在此将其主要功能设计如下: 1.LCD1602液晶显示当前感控杆状态开启/关闭,显示当前模式手动/自动&#xff1b…

网易有道开源 “子曰 - o1” 推理模型

网易有道开源的 “子曰 - o1” 推理模型支持消费级显卡,主要通过以下技术实现: 轻量级模型设计:“子曰 - o1” 采用 14B 参数规模,相比许多对硬件配置要求高的大型推理模型,选择了较小的参数规模,从基础上降…