枚举 enum,用于从众多选项中选择一个。

定义枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[derive(Debug)]
enum Week {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}

fn main() {
let today = Week::Saturday; // 使用枚举
let tomorrow = Week::Sunday;

println!("{:?}", today);
}

这是我们在很多面向对象语言中常见的定义枚举的方式

以往对枚举的认识,就是枚举限定了几个固定的选项,我们只能使用众多选项中的一个,或者说,只是使用了某一个元素的名字,例如上面Week枚举中的Monday,至于Monday是什么,无所谓,Monday = 1也好,Monday = 10也好,我们并不关心。但是对于Rust,我们对枚举有更进一步的应用。

用枚举代替结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[derive(Debug)]
enum IpAddr {
V4(u8, u8, u8, u8), // 这个枚举成员是四个u8类型的元祖
V6(String), // 这个枚举成员是String类型
}

fn main() {
// 定义一个枚举变量,并将四个值存放
let loopbackV4 = IpAddr::V4(127, 0, 0, 1);

// 定义另一个枚举变量,存入一个 String 类型的值
// "xxx".to_string() 方法和 String::from("xxx") 是一样的效果
let loopbackV6 = IpAddr::V6("::1".to_string());

println!("{:?}\n{:?}", loopbackV4, loopbackV6);
}

上面的代码,我们可以给枚举的每一个成员,指定一个数据类型,并且在创建一个枚举变量的时候,将某个值存入枚举。在 struct 中可以存储不同类型的变量,现在,在枚举中也可以。

再来看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 定义一个操作枚举
#[derive(Debug)]
enum Operation {
Move {x: i32, y:i32},
Jump(u32),
Attack(i32),
}

fn main() {
// 定义一个移动的操作
let opt_move = Operation::Move {x: 10, y: 11};

// 定义一个攻击的操作
let opt_attack = Operation::Attack(100);

// 定义一个跳跃的操作
let opt_jump = Operation::Jump(3);

DoOperation(opt_move);
DoOperation(opt_attack);
DoOperation(opt_jump);
}

// 执行操作
fn DoOperation(opt: Operation) {
println!("Do operation: {:?}", opt);
}

上面的代码我们定义了一个 Operation 枚举,里面有移动,攻击和跳跃三种操作方式。在 main 中定义了三个操作的变量,并且将每一次操作的具体值直接附加到了枚举成员上,例如 opt_attack 攻击操作,这次操作的伤害是100。

Rust 中使用 enum 代替 struct 将获得更简洁的代码。并且,每个枚举成员可以处理不同类型和数量的数据。

Rust 的 Option 枚举解释

Rust 中没有 Null 值,无法将一个变量赋值为 Null, 例如 let a = Null;,这样的操作在Rust中不存在。但是Rust中有 Option 枚举,这个枚举,用于表示 存在不存在 的概念。有点抽象,没关系,一步一步来,先看下 Option 源代码的定义

1
2
3
4
enum Option<T> {
Some(T),
None,
}

这里的 <T> 是指可以代表任何数据类型的,这是范型相关的东西,后面会学习。可以将 Option 枚举想象成可以装不同类型东西的小盒子,例如我们定义了一个装玩具汽车的小盒子,这个小盒子里只能装玩具汽车。任何时候,只要这个盒子存在,那么里面就会有两种状态,要么有玩具汽车,要么没有玩具汽车。在有些面向对象的语言中,如果访问一个玩具汽车,而恰好当时那里没有玩具汽车,那么就会造成空引用,如果没有手动处理空引用的情况,则程序就会出现Bug。而Rust则避免了 空引用 的情况。

看下面的代码

1
2
3
4
fn main(){
// 使用 Option 将一个 String 类型的值包起来
let name: Option<String> = Some("Fred".to_string());
}

Option 用于某些地方可能存在有值或没值的情况。Option 及成员已经被自动包含,所以我们不需要 Option::Some(xxx) 这样来使用。

match 匹配

对于 enum 类型的值,我们不能直接比较,看下面的代码,是无法编译通过的。

1
2
 let name: Option<String> = Option::Some("Jack".to_string());
println!(name == "Jack".to_string());

上面代码中 name == "Jack".to_string() 编译出错,因为 == 两边的数据类型不一样。这里,我们就可以用到 match

看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#[derive(Debug)]
enum Operation {
Move {x: i32, y:i32},
Jump(u32),
Attack(i32),
Talk(String),
}

fn main() {
let opt_talk = Operation::Talk("Hello".to_string());
let opt_move = Operation::Move { x: 10, y: 20 };

match opt_move {
Operation::Talk(ref value) => { // 这里加了 ref 是为了避免所有权转移
println!("Talk: {:?}", value);
},
Operation::Move {x,y} => {
println!("Move, x: {}, y: {}", x, y);
}
_ => {
// nothing
}
}
}

上面的代码中,match Operation 枚举时,并没有匹配所有的情况,所以最后需要 _ => ,相当于某些编译语言中 switch 中的 default,即在上面的情况都不匹配的情况下,执行的操作。

if let 使用

直接看代码,简化上面 match 的操作

1
2
3
4
5
if let Operation::Move{x, y} = opt_move {
println!("Move, x: {}, y: {}", x, y);
} else {
println!("nothing");
}

这样就可以不用 match 直接匹配枚举中的某一个成员类型。

感觉这篇博客有些地方写的可能不是很清楚,说明我对这块知识的理解程度还不够。下面是一些讲解 Rust 枚举的链接

https://rustwiki.org/zh-CN/rust-by-example/custom_types/enum.html

https://www.twle.cn/c/yufei/rust/rust-basic-enums.html

http://www.ameyalokare.com/rust/2017/10/23/rust-options.html