The Rust Programming Language:Enums and Pattern Matching
Defining an Enum
定义一个枚举:
1 | enum IpAddrKind { |
Enum Values
可以这样使用枚举值:
1 | let four = IpAddrKind::V4; |
这里我们只保存了IP 的类型,没有保存具体的 IP 地址。我们可以用刚学的结构体来实现:
1 | enum IpAddrKind { |
但实际上,我们可以直接把数据存储到每个枚举值当中:
1 | enum IpAddr { |
这里可以看到,对于每个枚举值而言,我们相当于也自动地得到了它们的构造函数。
使用枚举而不是结构体的另一个好处是:每个枚举值的关联数据可以有不同的数量和类型。
1 | enum IpAddr { |
就像我们可以在结构体上定义方法一样,我们也可以在枚举上定义方法:
1 | enum Message { |
The Option
Enum and Its Advantages Over Null Values
这是另一个在标准库中定义的枚举(上面提到的IpAddr
其实在标准库已经有定义)。
Rust没有空值,但它有一个可以编码值存在或不存在概念的枚举。这个枚举就是Option<T>
,它由标准库定义如下:
1 | enum Option<T> { |
它会自动被预加载,不需要显式引入。
这里的 <T>
表示泛型。
一些 Option
的使用示例:
1 | let some_number = Some(5); |
但是当我们有一个None
值时,它和空值的语义是相同的,那为什么说Option<T>
是比 null 更好的呢?
简单来说,因为Option<T>
和T
是不同的类型,如果我们有一个确定的有效值时,编译器就不会允许我们使用Option<T>
。例如以下的代码就是不能成功编译的:
1 | let x: i8 = 5; |
换句话说,你必须在做T
的操作之前把Option<T>
转换为T
。编译器会确保我们在使用这个值之前处理为空的情况。
The match
Control Flow Construct
示例:
1 | enum Coin { |
如果在某个 case 中需要运行多行代码,则需要用大括号,此时分割的逗号是可选的,例如:
1 | fn value_in_cents(coin: Coin) -> u8 { |
Patterns That Bind to Values
另一个有用的特性是,match arms 可以绑定 pattern 的部分值。例如:
1 |
|
如果调用value_in_cents(Coin::Quarter(UsState::Alaska))
,那么coin
的值就是Coin::Quarter(UsState::Alaska)
,state
的值就是UsState::Alaska
。
Matching with Option<T>
1 | fn plus_one(x: Option<i32>) -> Option<i32> { |
Matches Are Exhaustive
match
中的 arms’ patterns 必须 cover 所有情,以下代码无法编译通过:
1 | fn plus_one(x: Option<i32>) -> Option<i32> { |
因为我们没有处理None
的case。
Catch-All Patterns and the _
Placeholder
1 | let dict_roll = 9; |
other
catch 了所有的其余情况。
Concise Control Flow with if let
and let else
1 | let config_max = Some(3u8); |
比起match
,if let
简便了很多,但也失去了match
对所有情况的检查机制。
if let
可以和 let
一起用:
1 | let mut count = 0; |
Staying on the “Happy Path” with let...else
1 | fn describe_state_quarter(coin: Coin) -> Option<String> { |
else
arm 中必须从这个函数 return