Rust 程序设计语言学习——枚举模式匹配
枚举(enumerations),也被称作 enums。match
允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。
1 枚举的定义
假设我们要跨省出行,有多种交通工具供选择。常用的交通工具有飞机、火车、汽车和轮船。这是我们常用的跨省出行乘坐交通工具的所有形式:所以可以枚举出所有可能的值,这也正是此枚举名字的由来。
可以通过在代码中定义一个 Vehicle 枚举来表现这个概念并列出可能的交通工具类型,Airplane(飞机)、Train(火车)、Car(汽车) 和 Ship(轮船) 。这被称为枚举的成员(variants):
enum Vehicle {Airplane,Train,Car,Ship,
}
如果现在我们要区分汽车到底是哪一种?按照动力类型分类为汽油车、柴油车、混合动力车、电动车和燃料电池车。将动力类型和汽车做关联,修改后的枚举如下:
enum Vehicle {Airplane,Train,Car(PowerType),Ship,
}enum PowerType {Gasoline,Diesel,Mix,Electric,FuelCell,
}
实际上枚举的成员中可内嵌多种多样的类型,比如下面的例子,一个 Message 枚举,其每个成员都存储了不同数量和类型的值。
enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(i32, i32, i32),
}
这个枚举有四个含有不同类型的成员:
- Quit 没有关联任何数据。
- Move 类似结构体包含命名字段。
- Write 包含单独一个 String。
- ChangeColor 包含三个 i32。
定义一个如上例中所示那样的有关联值的枚举的方式和定义多个不同类型的结构体的方式很相像,除了枚举不使用 struct
关键字以及其所有成员都被组合在一起位于 Message
类型下。如下这些结构体可以包含与之前枚举成员中相同的数据:
struct QuitMessage; // 类单元结构体
struct MoveMessage {x: i32,y: i32,
}
struct WriteMessage(String); // 元组结构体
struct ChangeColorMessage(i32, i32, i32); // 元组结构体
不过,如果我们使用不同的结构体,由于它们都有不同的类型,我们将不能像使用上例中定义的 Message
枚举那样,轻易的定义一个能够处理这些不同类型的结构体的函数,因为枚举是单独一个类型。
结构体和枚举还有另一个相似点:就像可以使用 impl
来为结构体定义方法那样,也可以在枚举上定义方法。这是一个定义于我们 Message
枚举上的叫做 call
的方法:
impl Message {fn call(&self) {// 在这里定义方法体}}let m = Message::Write(String::from("hello"));m.call();
方法体使用了 self
来获取调用方法的值。这个例子中,创建了一个值为 Message::Write(String::from("hello"))
的变量 m
,而且这就是当 m.call()
运行时 call
方法中的 self
的值。
2 match 控制流结构
match
允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成。
可以把 match
表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会通过 match
的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。
比如我们想得到从一个城市到另一个城市的票价,不同的交通工具价格是不一样的。假设这两座城市无法通过轮渡的方式运输,所以下面的示例中返回了 -1,表征无效。
enum Vehicle {Airplane,Train,Car,Ship,
}fn fare(vehicle: Vehicle) -> i32 {match vehicle {Vehicle::Airplane => 800,Vehicle::Train => 100,Vehicle::Car => 200,Vehicle::Ship => -1,}
}
这看起来非常像 if
所使用的条件表达式,不过这里有一个非常大的区别:对于 if
,表达式必须返回一个布尔值,而这里它可以是任何类型的。
当 match
表达式执行时,它将结果值按顺序与每一个分支的模式相比较。如果模式匹配了这个值,这个模式相关联的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支,非常类似一个硬币分类器。可以拥有任意多的分支。每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 match
表达式的返回值。如果分支代码较短的话通常不使用大括号。如果想要在分支中运行多行代码,可以使用大括号,而分支后的逗号是可选的。
enum Vehicle {Airplane,Train,Car,Ship,
}fn fare(vehicle: Vehicle) -> i32 {match vehicle {Vehicle::Airplane => 800,Vehicle::Train => 100,Vehicle::Car => 200,Vehicle::Ship => {println!("Not supported by ships!");-1}}
}
绑定值的模式
匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值。这也就是如何从枚举成员中提取值的。
下面的例子运行后会打印出汽车的动力类型时混动(Mix)。
#[derive(Debug)]
enum PowerType {Gasoline,Diesel,Mix,Electric,FuelCell,
}enum Vehicle {Airplane,Train,Car(PowerType),Ship,
}fn fare(vehicle: Vehicle) -> i32 {match vehicle {Vehicle::Airplane => 800,Vehicle::Train => 100,Vehicle::Car(powerType) => {println!("The power type of the car is {:?}!", powerType);200},Vehicle::Ship => {println!("Not supported by ships!");-1}}
}fn main() {fare(Vehicle::Ship);fare(Vehicle::Car(PowerType::Mix));
}
运行结果:
Not supported by ships!
The power type of the car is Mix!
匹配 Option
Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是 Option<T>
,而且它定义于标准库中,如下:
enum Option<T> {None,Some(T),
}
Option<T>
枚举是如此有用以至于它甚至被包含在了 prelude 之中,你不需要将其显式引入作用域。另外,它的成员也是如此,可以不需要 Option::
前缀来直接使用 Some
和 None
。即便如此 Option<T>
也仍是常规的枚举,Some(T)
和 None
仍是 Option<T>
的成员。
<T>
语法是一个我们还未讲到的 Rust 功能。它是一个泛型类型参数。
比如我们想要编写一个函数,它获取一个 Option<i32>
,如果其中含有一个值,将其加一。如果其中没有值,函数应该返回 None
值,而不尝试执行任何操作。
fn plus_one(x: Option<i32>) -> Option<i32> {match x {None => None,Some(i) => Some(i + 1),}
}fn main() {let five = Some(5);let six = plus_one(five);let none = plus_one(None);println!("six is {:?}, none is {:?}", six, none);
}
运行结果:
six is Some(6), none is None
如果去除上例中匹配分支内 None => None
这句代码,实际上会报错。
Rust 中的匹配是穷尽的(exhaustive):必须穷举到最后的可能性来使代码有效。
fn plus_one(x: Option<i32>) -> Option<i32> {match x {Some(i) => Some(i + 1),}
}fn main() {let five = Some(5);let six = plus_one(five);let none = plus_one(None);println!("six is {:?}, none is {:?}", six, none);
}
运行结果:
Exited with status 101Standard ErrorCompiling playground v0.0.1 (/playground)
error[E0004]: non-exhaustive patterns: `None` not covered--> src/main.rs:2:11|
2 | match x {| ^ pattern `None` not covered|
note: `Option<i32>` defined here--> /playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:570:1|
570 | pub enum Option<T> {| ^^^^^^^^^^^^^^^^^^
...
574 | None,| ---- not covered= note: the matched value is of type `Option<i32>`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown|
3 ~ Some(i) => Some(i + 1),
4 ~ None => todo!(),|For more information about this error, try `rustc --explain E0004`.
error: could not compile `playground` (bin "playground") due to 1 previous error
通配模式和 _ 占位符
我们希望对一些特定的值采取特殊操作,而对其他的值采取默认操作。比如下面的例子,只有飞机和火车给出票价,不支持其他交通方式。
#[derive(Debug)]
enum Vehicle {Airplane,Train,Car,Ship,
}fn fare(vehicle: Vehicle) -> i32 {match vehicle {Vehicle::Airplane => 800,Vehicle::Train => 100,other => {println!("Not support {:?}", other);-1}}
}fn main() {let price = fare(Vehicle::Airplane);println!("The price of air tickets is {}", price);fare(Vehicle::Car);
}
运行结果:
The price of air tickets is 800
Not support Car
对于前两个分支,匹配模式是字面值 Vehicle::Airplane
和 Vehicle::Train
,最后一个分支则涵盖了所有其他可能的值,模式是我们命名为 other 的一个变量。
即使我们没有列出 Vehicle
所有可能的值,这段代码依然能够编译,因为最后一个模式将匹配所有未被特殊列出的值。这种通配模式满足了 match
必须被穷尽的要求。请注意,我们必须将通配分支放在最后,因为模式是按顺序匹配的。如果我们在通配分支后添加其他分支,Rust 将会警告我们,因为此后的分支永远不会被匹配到。
Rust 还提供了一个模式,当我们不想使用通配模式获取的值时,请使用 _
,这是一个特殊的模式,可以匹配任意值而不绑定到该值。这告诉 Rust 我们不会使用这个值,所以 Rust 也不会警告我们存在未使用的变量。
#[derive(Debug)]
enum Vehicle {Airplane,Train,Car,Ship,
}fn fare(vehicle: Vehicle) -> i32 {match vehicle {Vehicle::Airplane => 800,Vehicle::Train => 100,_ => {println!("Not support");-1}}
}fn main() {let price = fare(Vehicle::Airplane);println!("The price of air tickets is {}", price);fare(Vehicle::Car);
}
运行结果:
The price of air tickets is 800
Not support
这个例子也满足穷举性要求,因为我们在最后一个分支中明确地忽略了其他的值。我们没有忘记处理任何东西。
3 if let 简洁控制流
if let
语法让我们以一种不那么冗长的方式结合 if
和 let
,来处理只匹配一个模式的值而忽略其他模式的情况。
enum Vehicle {Airplane,Train,Car,Ship,
}fn fare(vehicle: Vehicle) -> i32 {if let Vehicle::Airplane = vehicle {println!("Travel by plane");return 800;}return -1;
}fn main() {let mut price = fare(Vehicle::Airplane);println!("The price of air tickets is {}", price);price = fare(Vehicle::Car);println!("The price of car tickets is {}", price);
}
运行结果:
Travel by plane
The price of air tickets is 800
The price of car tickets is -1
if let
语法获取通过等号分隔的一个模式和一个表达式。它的工作方式与 match
相同,这里的表达式对应 match
而模式则对应第一个分支。在这个例子中,模式是 Vehicle::Airplane
。就跟在对应的 match
分支中一样。模式不匹配时 if let
块中的代码不会执行。
使用 if let
意味着编写更少代码,更少的缩进和更少的样板代码。然而,这样会失去 match
强制要求的穷尽性检查。match
和 if let
之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。
换句话说,可以认为 if let
是 match
的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。
还可以在 if let
中包含一个 else
。
enum Vehicle {Airplane,Train,Car,Ship,
}fn fare(vehicle: Vehicle) -> i32 {if let Vehicle::Airplane = vehicle {println!("Travel by plane");return 800;} else {return -1;}
}fn main() {let mut price = fare(Vehicle::Airplane);println!("The price of air tickets is {}", price);price = fare(Vehicle::Train);println!("The price of train tickets is {}", price);
}
运行结果:
Travel by plane
The price of air tickets is 800
The price of train tickets is -1
参考链接
- Rust 官方网站:https://www.rust-lang.org/zh-CN
- Rust 官方文档:https://doc.rust-lang.org/
- Rust Play:https://play.rust-lang.org/
- 《Rust 程序设计语言》
相关文章:

Rust 程序设计语言学习——枚举模式匹配
枚举(enumerations),也被称作 enums。match 允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。 1 枚举的定义 假设我们要跨省出行,有多种交通工具供选择。常用的交通工具有飞机、火车、汽车和轮…...

正则表达式(1)
文章目录 专栏导读1、match2、匹配目标3、通用匹配4、常用匹配规则表格 专栏导读 ✍ 作者简介:i阿极,CSDN 数据分析领域优质创作者,专注于分享python数据分析领域知识。 ✍ 本文录入于《python网络爬虫实战教学》,本专栏针对大学生…...
nginx + keepalived 搭建教程
1.安装依赖 yum install -y keepalived systemctl start keepalived systemctl enable keepalived 2.配置 a. keepalived.conf配置 global_defs {router_id nginx_server2 # 机器标识(backup节点为nfs_server2) }vrrp_script chk { script "/etc/keepalived/check_po…...
React事件和原生事件的执行顺序
在 React 中,事件处理分为两种类型:React 合成事件(Synthetic Event)和原生 DOM 事件(Native DOM Event)。它们的执行顺序略有不同。 React 合成事件 React 合成事件的执行顺序: React 合成事件…...
为什么在计算查询Q和键K的矩阵乘法时需要转置键矩阵K。示例说明q11,k11代表什么。线性变换矩阵 W_q 用于生成查询,W_k 用于生成键怎么获取的。
目录 为什么在计算查询Q和键K的矩阵乘法时需要转置键矩阵K。 示例说明q11,k11代表什么。...

剑指Offer题目笔记27(动态规划单序列问题)
面试题89: 问题: 输入一个数组表示某条街道上的一排房屋内财产的数量。相邻两栋房屋不能同时被盗,问小偷能偷取到的最多财物。 解决方案一(带缓存的递归): 解决方案: 由于有报警系统&…...

撸代码时,有哪些习惯一定要坚持?
我从2011年开始做单片机开发,一直保持以下撸代码的习惯。 1.做好代码版本管理 有些人,喜欢一个程序干到底,直到实现全部的产品功能,我以前做51单片机的项目就是这样。 如果功能比较多的产品,我不建议这样做࿰…...
【leetcode面试经典150题】17.罗马数字转整数(C++)
【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主,题解使用C语言。(若有使用其他语言的同学也可了解题解思路,本质上语法内容一致&…...

前后端开发之——文章分类管理
原文地址:前后端开发之——文章分类管理 - Pleasure的博客 下面是正文内容: 前言 上回书说到 文章管理系统之添加文章分类。就是通过点击“新建文章分类”按钮从而在服务端数据库中增加一个文章分类。 对于文章分类这个对象,增删改查属于配…...

第12届蓝桥杯省赛 ---- C/C++ C组
文章目录 1. ASC2. 空间3. 卡片4. 相乘5. 路径6.时间显示7.最少砝码8. 杨辉三角形9. 左孩子右兄弟 第12届蓝桥杯省赛,C/C C组真题,第10题不是很清楚,题解不敢乱放😁😁😁 1. ASC 额。。。。 #include <i…...

IVS模型解释
核心思路 【Implied volatility surface predictability: The case of commodity markets】 半参数化模型:利用各种参数(或者因子)对隐含波动率进行降维(静态参数化因子模型),对参数化因子的时间序列进行间接的建模 基于非对称…...

通用开发技能系列:Git
云原生学习路线导航页(持续更新中) 本文是 通用开发技能系列 文章,主要对编程通用技能Git进行学习 1.为什么使用版本控制系统 版本控制系统可以解决的问题 代码备份很重要版本控制很重要协同工作很重要责任追溯很重要 常见的版本控制系统 Gi…...

最新怎么订阅OnlyFans上喜欢的博主,详细教程
大家好,本文教大家如何用虚拟信用卡在 Onlyfans 订阅,链接在浏览器打开地址https://bewildcard.com/i/GPT310,虚拟卡开好之后,用支付宝充值就可以进行订阅OnlyFans平台的博主了。 什么是OnlyFans? OnlyFans 是一个提…...

Mysql故障和优化
一、MySQL故障 二、MySQL优化 1.硬件优化: 2.数据库设计与规划 1.提前估计数据量,使用什么存储引擎 2.数据库服务器专机专用,避免额外的服务可能导致的性能下降和不稳定性 3.增加多台服务器,以达到稳定、高效的效果。主从同步、…...

Windows系统C盘空间优化进阶:磁盘清理与Docker日志管理
Windows系统C盘空间优化进阶:磁盘清理与Docker日志管理 文章目录 Windows系统C盘空间优化进阶:磁盘清理与Docker日志管理磁盘清理工具 使用“运行”命令访问磁盘清理利用存储感知自动管理空间清理WinSxS文件夹结合手动清理策略 小结删除临时文件总结&…...

14届蓝桥杯 C/C++ B组 T7 子串简写 (字符串)
采用存储目标字符下标的方法,此题的想法比较新奇,故予以记录。 存好下标之后,可以先定位好启始的字符,然后去搜结尾字符符合长度k并且最靠近启始字符的下标,找到之后可以直接取到这个下标之后的所有下标,因…...

Android 系统大致启动流程
Android启动流程大体为:BootRom -> BootLoader -> Kernel -> Init -> Zygote -> SystemServer ->Launcher 1、Loader层 1.1、Boot ROM 电源按下,引导芯片代码开始从预定义的地方(固化在ROM)开始执行࿰…...

【Web】2024红明谷CTF初赛个人wp(2/4)
目录 ezphp playground 时间原因只打了2个小时,出了2道,简单记录一下 ezphp 参考文章 PHP filter chains: file read from error-based oracle https://github.com/synacktiv/php_filter_chains_oracle_exploit 用上面的脚本爆出部分源码ÿ…...

stable-diffusion-webui安装教程
现在AI开始进入绘画领域,并且能自动根据文本来创建图片出来,这是一个划时代的进步。 这时候,我也不能落后,要紧跟上时代的步伐,那么也来学习一下stable-diffusion的使用,这样也算多一项对技术的认识,提高对AI的认知。 从网上看到很多stable-diffusion-webui的安装,其…...

如何魔改 diffusers 中的 pipelines
如何魔改 diffusers 中的 pipelines 整个 Stable Diffusion 及其 pipeline 长得就很适合 hack 的样子。不管是通过简单地调整采样过程中的一些参数,还是直接魔改 pipeline 内部甚至 UNet 内部的 Attention,都可以实现很多有趣的功能或采样生图结果。 本…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...