枚举类型也称为 枚举
,允许我们列举所有可能的值来定义一个类型。在 Rust 的 match 表达式中使用模式匹配,可以根据不同的枚举值来执行不同的代码。
定义枚举
如下通过定义枚举 IpAddrKind
来列举出所有可能的 IP 地址种类,即 枚举变体
。枚举的变体全部位于其标识符的命名空间中,并使用两个冒号来将标识符和变体分隔开来。
1 | enum IpAddrKind { |
为了存储实际的 IP 地址数据,如果使用类似于 C 的编程语言,我们可能会写出如下代码:
1 | enum IpAddrKind { |
但是 Rust 的枚举允许我们直接将关联的数据嵌入枚举变体内,可以使用枚举来更简捷地表达上述概念,而不用将枚举集成到结构体中。如下 IpAddr 的定义中,V4 和 V6 两个变体都关联上了 String 值:
1 | enum IpAddr { |
直接将数据附加到枚举的每个变体中,这样就不需要额外地使用结构体了。另外一个使用枚举替代结构体的优势在于:每个变体可以拥有不同类型和数量的关联数据。可以在枚举的变体中嵌入任意类型的数据,无论是字符串、数值还是结构体,甚至可以嵌入另外一个枚举。
1 | struct Ipv4Addr { |
当我们使用结构体时,每个结构体都有自己的类型,我们无法轻易地定义一个能够统一处理这些类型数据的函数。而对于枚举的变体则没有这个问题,因为它们都是同一个类型。
和结构体类似,也是使用 impl
关键字定义枚举的方法。
Option
枚举及其在空值处理方面的优势
Option
类型定义于标准库中,它描述了一种值可能不存在的情形,所以它被广泛地应用于各种场合。将这一概念使用类型系统描述出来意味着,编译器可以自动检查我们是否妥善地处理了所有应该被处理的情况。使用这一功能可以避免在某些语言中极其常见的错误。
空值本身所尝试表达的概念仍然是有意义的,因为它代表了因为某种原因而变为无效或者缺失的值。引发问题的关键不是概念本身,而是那些具体的实现措施。Rust 中没有空值,但是提供了一个使用类似概念的枚举,可以用它来标识一个值无效或者缺失,该枚举就是 Option<T>
,它在标准库中的定义如下:
1 | enum Option<T> { |
Option<T>
及其变体都包含在预导入模块中。
1 | fn main() { |
当使用 None 而不是 Some 变体来赋值时,需要明确地告诉 Rust 该 Option<T>
的具体类型。这是因为单独的 None 变体值与持有数据的 Some 变体不一样,编译器无法根据这些信息来正确推导出值的完整类型。
Option<T>
和 T 是不同的类型,编译器不允许我们直接像使用普通值那样去使用 Option<T>
的值。当我们使用 Option<T>
时,我们必须考虑值不存在的情况,同时编译器会迫使我们在使用值之前正确地做出处理动作。为了使用 Option<T>
中可能存在的 T,必须将它转换为 T,这样能帮助我们避免使用空值时最常见的一个问题:假设某个值存在,实际上却为空。
为了持有一个可能为空的值,我们总是需要将它显式地放入对应类型的 OptionOption<T>
,我们就可以安全地假设这个值不是非空的。这是 Rust 为限制控制泛滥以增加 Rust 代码安全性而做出的一个有意为之的设计决策。
为了使用一个 Option<T>
值,必须要编写处理每个变体的代码。match 表达式就是这么一个可以用来处理枚举的控制流结构:它允许我们基于枚举拥有的变体来决定运行的代码分支,并允许代码通过匹配值来获取变体内的数据。
控制流运算符 match
Rust 的控制流运算符 match 允许将一个值与一系列的模式相比较,并根据匹配的模式执行相应代码。模式可以由字面量、变量名、通配符等组成。值会依次通过 match
中的模式,并在遇到第一个 符合
的模式时进入相关联的代码块,并在执行过程中被代码所使用。
1 | enum Coin { |
match
中的表达式可以返回任何类型match
中的分支由模式和它所关联的代码完成,模式与分支代码使用=>
运算符区分开来- 分支关联的代码也是一个表达式,它运行得到的结果值会作为整个 match 表达式的结果返回
- 如果一个分支所关联的代码包含多行,可以使用
{}
将它们包裹起来 - 不同分支之间使用逗号分隔
当 match 表达式执行时,它会将产生的结果依次与每个分支中的模式进行比较,如果匹配成功,则与该模式相关联的代码就会继续执行,而模式匹配失败,则会继续执行下一个分支。
匹配分支可以绑定被匹配对象的部分值,这也是我们用于从枚举变体中提取值的方法。
1 |
|
匹配 Option<T>
在使用 Option<T>
时,也可以使用 match 来从 Some 中取得其内部的值。
1 | fn plus_one(x: Option<i32>) -> Option<i32> { |
Rust 代码中将 match 与枚举相结合在许多情形下都是非常有用的:使用 match 来匹配枚举值,并将其中的值绑定到某个变量上,接着根据这个值执行相应的代码。
匹配必须穷举所有可能
Rust 中的匹配是穷尽的:必须穷尽所有可能来确保代码是合法有效的。有时候可能不想处理所有的可能值,_
模式可以匹配任何值,在它对应的代码块中,可以使用一个空元祖 ()
,这样什么都不会发生。
简单控制流 if let
if let
可以让我们通过一种不那么繁琐的语法结合使用 if 与 let,并处理那些只用关心某一种匹配而忽略其他匹配的情况。例如如下代码指向处理 Some(3)
,而其他情况都直接忽略。
1 | let some_u8_value = Some(0u8); |
if let
可以让上述代码更加简单:
1 | if let Some(3) = some_u8_value { |
if let
语法使用一对以 =
隔开的模式与表达式,它们所起的作用与 match
中完全相同,表达式对应 match
中的输入,而模式则对应第一个分支。使用 if let
更加简洁,但是也放弃了 match 所附带的穷尽性检查。
if let
搭配中还可以使用 else,else 所关联的代码块在 if let
语句中扮演的角色,类似于 match
中的 _
模式所关联的代码块一样。
1 | if let Some(3) = some_u8_value { |