枚举enumerations),也被称作 enums。枚举允许你通过列举可能的 成员variants) 来定义一个类型。

Rust 中的枚举与 F#OCamlHaskell 这样的函数式编程语言中的 代数数据类型algebraic data types)最为相似。

定义枚举

使用 enum 来定义枚举,与结构体第一个相似之处是,枚举也是一种自定义的数据类型。

枚举的成员可以与任意类型的数据绑定(或称之为存储)起来,以 IP 地址为例,V4V6 类型可分别与类型为 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 的语义更加清晰,请自作取舍。