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,都可以实现很多有趣的功能或采样生图结果。 本…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
