文章目录
前言
这一篇主要介绍基本的枚举和模式匹配,这两个语法点在Rust中被大量应用~
rust的枚举(enumerations, enums)与java的枚举相比功能更强,它的枚举更接近Haskell等语言的代数数据类型。
rust的枚举定义时除了给出各个枚举成员的名称外,还可以为各个枚举成员提供一个类型信息,也就是说rust的枚举成员实际上是类型。这种设计可以用于模式匹配。
通常会把枚举的值称为枚举的变体,比如下面的枚举类型IpAddrKind
,它有两个变体V4
和V6
:
enum IpAddrKind {
V4, V6
}
5.1 定义枚举及使用
5.1.1 基本语法
- 语法:
enum EnumName { NUMBER1, NUMBER2, ... NUMBERN, }
- 定义枚举的成员时最后一个成员后面可以跟逗号,统一规范。
- 使用枚举成员(变体,variant):
EnumName::NUMBERX
enum UpAddrKind { V4, V6 } let four = IpAddrKind::V4; let six = IpAddrKind::V6;
- 枚举类型作为变量类型,也可以作为结构体中的字段类型,即可以将枚举类型的变体作为变量值或者结构体实例的字段值使用:
struct IpAddr { kind: IpAddrKind, address: String } let home = IpAddr { kind: IpAddrKind::V4, address: String::from("127.0.0.1"), }
5.1.2 将数据附加到枚举的变体中
-
定义枚举类型的时候可以在不同的变体后面给出自定义的类型,则在使用该枚举类型的不同变体时,可以将数据附加到变体上。语法如下:
enum EnumName { NUMBER0, // 不带附带信息 NUMBER1(String), // 单值类型 NUMBER2(i32, i32, i32), // 元组等多只类型 NUMBER3(StructName), // 结构体等复合类型 NUMBER4{x: i32, y: i32}, // 匿名结构体(注意没有圆括号包着) // 甚至可以嵌入其他枚举类型 }
-
优点:
- 不需要额外使用struct来存储相关的数据。
- 每个变体可以拥有不同的类型以及关联的数据量。
-
注意:如果一个枚举类型的变体设置了附加数据的自定义类型,则在使用该变体时需要附上相同类型的数据。
-
例子:
enum IpAddrKind { V4(u8,u8,u8,u8), V6(String), } fn main() { let four = IpAddrKind::V4(127,0,0,1); let six = IpAddrKind::V6(String::from("::1")); }
5.1.3 为枚举类型定义方法
- 枚举类型与结构体类型一样可以通过
impl
块定义自己的方法。enum EnumName { NUMBER1(i32), NUMBER2(String), } impl EnumName { fn call(&self) { // 代码逻辑 } } let m = EnumName::NUMBER1(10); m.call();
5.2 Option枚举
Option
枚举定义于标准库中,且在Prelude(预导入模块)中。Option
枚举用于描述:某个值可能存在(某种类型)或者不存在。- rust中没有空值
null
,取而代之的是使用标准库中的Option<T>
枚举来检查或使用空值的概念。 Option
的定义如下:enum Option<T> { Some(T), None, }
T
为泛型None
表示没有值,即空值的概念Some(T)
表示有值,且值为T
类型
- 由于
Option
非常重要,所以已被包含在prelude
中,且它的成员不需要使用Option::
前缀就能使用,因为太常用了。 Option
提供了很多方法,针对不同的情况来取出有效值或检查值是否有效,具体还是要看文档。- 通常要取出
Some(T)
中值来使用,可以通过模式匹配的形式获得并使用。 Option<T>
比Null好在哪里:Option<T>
和T
是不同的类型,不可以把Option<T>
直接当成T
。
5.3 match 控制流运算符
5.3.1 基本用法
- match允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码。
- 模式可以是字面值、变量名、通配符…。match常用来检查枚举值的属于哪一种成员类型,并根据其成员类型跳转不同分支执行表达式,并且执行完以后可以返回其中一条分支的表达式值(如果这个分支有很多语句,可以用花括号包裹,只要保证最后又表达式即可)。
let val = match enum_value { EnumName::NUMBER1 => exp1, EnumName::NUMBER2 => exp2, EnumName::NUMBER3 => { // 各种语句 exp3 }, }
- 需要注意的是各个分支的表达式类型值要兼容。
- 例子:
enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } }
- **注意:匹配是穷尽的,即必须在代码中各处各种枚举成员出现时对应的分支处理。**但是在实际使用中开发者并不会关注所有的分支情况,此时可以使用通配符
_
来匹配默认情况(需要放在所有分支之后),即当所有手动给出的情况都不匹配的时候,就走_
分支。let some_u8_value = 0u8; match some_u8_value { 1 => println!("one"), 3 => println!("three"), 5 => println!("five"), 7 => println!("seven"), _ => (), // 返回unit值,统一各个分支的返回情况 }
5.3.2 绑定值的模式
match匹配的分支可以绑定到被匹配对象的部分值。通过这个功能可以从enum变体中提取值。具体语法为:
enum EnumName {
NUMBER0, // 不带附带信息
NUMBER1(String), // 单值类型
NUMBER2(i32, i32, i32), // 元组等多只类型
NUMBER3(StructName), // 结构体等复合类型
NUMBER4{x: i32, y: i32}, // 匿名结构体
}
match enum_val {
EnumName::NUMBER1(s) => {
/* 提取出String类型的附属信息 */
exp1,
},
EnumName::NUMBER2(t) => {
/* 提取出(i32, i32, i32)类型的附属信息 */
exp2,
},
EnumName::NUMBER3(变量名) => {
// 变量为对应附属信息的类型
exp3,
},
// 其他分支
}
5.3.3 匹配 Option<T>
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
5.4 if let 简单控制流
- if let控制流是match的语法糖,用于处理只匹配一个模式的值并忽略其他模式的情况。
- 优点:更少的代码,更少的缩进,更少的模板代码。
- 缺点:放弃了穷举的可能。
- 语法:
if let 枚举类型变种(附属变量名) = 枚举值 { // 匹配成功后的逻辑,可使用 附属变量名 中的附属信息 } else { // 其他模式的分支,不一定需要 }
- 例子:
let mut count = 0; if let Coin::Quarter(state) = coin { println!("State quarter from {:?}!", state); } else { count += 1; }