枚举(enumerations),也被称作 enums。枚举允许你通过列举可能的 成员(variants) 来定义一个类型。
Rust 中的枚举与 F#、OCaml 和 Haskell 这样的函数式编程语言中的 代数数据类型(algebraic data types)最为相似。
定义枚举
使用 enum
来定义枚举,与结构体第一个相似之处是,枚举也是一种自定义的数据类型。
枚举的成员可以与任意类型的数据绑定(或称之为存储)起来,以 IP 地址为例,V4
和 V6
类型可分别与类型为 String
的地址字符串数据绑定在一起。而且,每个成员所绑定的类型可以互相不同。
与结构体第二个相似之处是,枚举也可通过 impl 来定义枚举的方法,使用 ::
来访问成员,使用 .
来访问方法或关联函数。
Option
是标准库提供的一个非常常见且实用的枚举。Option
类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。因此,Rust 并没有很多其他语言中有的空值(Null)功能,大多数需要表示空值的情况都使用 Option
枚举类型来代替。
enum Option<T> {
Some(T),
None,
}
<T>
是一个泛型类型参数,可以简单地认为 Some
成员可以绑定任意类型的数据,之后的内容会进行详细介绍。
理解 Option 枚举对于进一步体会 Rust 的设计思想十分重要,首先,下面的代码是错误的,当 y
被定义为 Option
类型时,Rust 编译器无法确保其值一直有效,因此 y
无法与可以确保一直有效的 x
进行运算。所以,开发者就不必去担心假设某值不为空但实际上为空的情况。
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
为了使用 Option<T>
值,需要编写处理每个成员的代码,可以使用 match
表达式来处理枚举的控制流结构:对于枚举的匹配,它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。
match
控制流运算符
match
是一个极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成(后续的内容会详细阐述所有不同种类的模式)。match
确保了所有可能的情况都可以得到处理。
这里需要对模式有一个清晰的理解,什么叫模式?模式其实就是一个值。
由于后续内容会对所有模式做深入阐述,此处只说明对于枚举的模式匹配来说模式指的是什么。很简单,据前面笔记记录的,这里的模式就是枚举的成员及其绑定的值。
成员所绑定的值会传给模式其后的表达式或是代码块,表达式或是代码块的返回值最终作为 match
表达式的值,因此 match 的每个分支返回值类型需要一致。
而对 Option
枚举的 match
使用特别常用,需要特别注意 Rust 中的匹配是 穷尽的(exhaustive):必须穷举到最后的可能性来使代码有效,可以使用通配符 _
来匹配剩余的未显式说明的模式。
if let
简洁控制流
当 match
只关心枚举中的一个时会有些啰嗦,Rust 还为此提供了一个语法糖 if let
,来简化语法。对比一下下面的两段代码,两者行为是一致的(match
只关心当值为 Some(3)
时执行代码)。
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
println!("three");
}
单用 if let
会失去 match
强制要求的穷尽性检查,可以通过添加 else
语句来弥补,if let
更加简洁,而match
的语义更加清晰,请自作取舍。