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,都可以实现很多有趣的功能或采样生图结果。 本…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...