这篇博客涉及到的知识点有 枚举的定义Option 枚举match 语法if let 语法

枚举的定义

Rust 中定义一个枚举,使用关键字 enum,看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义一个NPC类型枚举
enum NPCType {
TalkNPC,
WalkNPC,
BattleNPC,
TaskNPC,
}

enum GameState {
OnLogin = 1,
OnHall = 2,
OnWorld,
OnBattle,
OnLogOut,
}

但是 Rust 的枚举是可以关联数据的,看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum CommandType {
Move(i32, i32, i32),
Jump(u32),
ChangeColor(u8, u8, u8, u8),
Smile,
}

fn main() {
let move_command = CommandType::Move(10, -5, 3);
let jump_command = CommandType::Jump(2);
let change_color_command = CommandType::ChangeColor(110,128,55,255);
let smile_command = CommandType::Smile;
}

上面的代码中,可以给枚举中的字段关联数据,例如,Move 关联了3个i32类型的数据。我们可以关联任何类型的数据,也可以是自己定义的结构体,或者另一个枚举,都是可以的。

Option 枚举

Rust 没有空值(Null),但是可以使用 Option 枚举,来表示有和没有的概念。Option 枚举有两个字段,一个是 Some, 一个是 None,而 Option 是泛型的,就是说它可以面向任何类型的值 Option<T>,用于表示任何类型的值,有值,或者没有值。Some(T) 表示有值,而 None 表示没有值,以 i32 为例子,看下面的代码

1
2
3
4
5
6
7
fn main() {
let has_value: Option<i32> = Some(200);
let none_value: Option<i32> = None;

println!("has_value: {:?}", has_value);
println!("none_value: {:?}", none_value);
}

Option 可以理解为盒子,Some(T) 表示有盒子,如果有盒子,那盒子里一定装了某种东西,也就是某种类型的值。而 None 表示没有盒子。上面的代码中,直接使用了 Some(200),而不是 Option::Some(200),是因为太常用,而被 Rust 包含在了 preload 中,所以我们可以省略前缀 Option::。在 Rust 的学习过程中,会经常遇到 Option。特别是函数的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let girl_age = get_girl_age(true);
println!("girl_age: {:?}", girl_age); // Some(18)

let boy_age = get_girl_age(false);
println!("boy_age: {:?}", boy_age); // None
}

fn get_girl_age(is_girl: bool) -> Option<u32> {
if is_girl {
Some(18)
} else {
None
}
}

match 语法

match 语法有点类型其他语言的 switch 语法,用于条件匹配。直接看代码

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
28
29
30
31
enum CommandType {
Move(i32, i32, i32),
Jump(u32),
ChangeColor(u8, u8, u8, u8),
Smile,
}

fn main() {
let move_command = CommandType::Move(10, 5, 2);
process_command(&move_command);

let change_color_command = CommandType::ChangeColor(154,220, 14, 255);
process_command(&change_color_command);
}

fn process_command(command: &CommandType) {
match command {
CommandType::Move(x, y, z) => {
println!("Player Move, x: {} y: {} z: {}", x, y, z);
},
CommandType::Jump(height) => {
println!("Player Jump to: {}", height);
},
CommandType::ChangeColor(r, g, b, a) => {
println!("player change color: ({}, {}, {}, {})", r, g, b, a);
},
CommandType::Smile => {
println!("player smile");
}
}
}

上面的代码中,还是以 CommandType 枚举来举例。在 main 函数中定义了两个 CommandType 变量,分别使用不同的枚举,然后在 process_command 中,使用 match 语法匹配。在使用 match 匹配时, 一但类型匹配成功,就可以将其关联的值,绑定到某个变量上,然后在 match 逻辑中使用。例如上面代码中匹配上 CommandType::Move,Move 关联了三个 i32 类型的数据,所以在匹配时,就可以将关联的数据,绑定到 match 中的 x, y, z 三个变量上。

Rust 的 match 匹配必须覆盖所有的类型,如果逻辑中只关心某些类型,则可以使用 _ 忽略其他类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum CommandType {
Move(i32, i32, i32),
Jump(u32),
ChangeColor(u8, u8, u8, u8),
Smile,
}

fn main() {
let command = CommandType::Smile;

match command {
CommandType::Smile => {
println!("Smile Command");
},
_ => {
println!("Other Command");
}
}
}

if let 语法

相比于 match 匹配的繁琐,if let 语法让匹配更简洁一些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum CommandType {
Move(i32, i32, i32),
Jump(u32),
ChangeColor(u8, u8, u8, u8),
Smile,
}

fn main() {

let move_command = CommandType::Move(10, 5, 2);

if let CommandType::Move(x, y, z) = move_command {
println!("Player Move: x: {}, y: {}, z: {}", x, y, z);
} else {
println!("is not move command");
}
}

上面的代码中,if let 可以理解为,将右边的 move_command 和左边的枚举类型匹配,如果能匹配上,则将 move_command中的数据绑定到 x, y, z 上,然后执行匹配成功的逻辑,也就是打印出 x, y, z 的值,如果类型匹配不上,则执行匹配不上的逻辑。

为了更好的理解,下面再看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn main() {
let x = 2;

if x == 2 {
println!("x = 2");
} else {
println!("x != 2");
}

if let 2 = x {
println!("x = 2");
} else {
println!("x != 2");
}
}

上面代码中 if 语句 和 if let 语句执行的打印是一样的,似乎是一个正向匹配测试,一个反向匹配测试的感觉。