0%

Rust 学习笔记:第6章 枚举与模式匹配

枚举

定义枚举

  • 枚举允许我们列举所有可能的值来定义一个类型

定义一个 IP 地址(IPv4、IPv6)的枚举

1
2
3
4
enum IpAddrKind {
V4,
V6,
}

创建枚举值

枚举名加上::再加上值就可以创建枚举值

1
2
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

枚举作为函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum IpAddrKind {
V4,
V6,
}

fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

route(four);
route(six);
route(IpAddrKind::V6);
}

fn route(ip_kind: IpAddrKind) {}

数据附加到枚举的变体中

  • 不需要额外使用 struct
  • 每个变体可以拥有不同的类型以及关联的数据量
1
2
3
4
5
6
7
8
9
enum IpAddrKind {
V4(u8, u8, u8, u8),
V6(String),
}

fn main() {
let home = IpAddrKind::V4(127, 0, 0, 1);
let loopback = IpAddrKind::V6(String::from("::1"));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
enum Message {
Quit,
Move {x: i32, y: i32},
Write(String),
ChangeColor(i32, i32, i32),
}

fn main() {
let q = Message::Quit;
let m = Message::Move {x: 12, y: 24};
let w = Message::write(String::from("hello"));
let c = Message::ChangeColor(0, 255, 255);
}

为枚举定义方法

  • 使用 impl 关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum Message {
Quit,
Move {x: i32, y: i32},
Write(String),
ChangeColor(i32, i32, i32),
}

impl Message {
fn call(&self) {}
}

fn main() {
let q = Message::Quit;
let m = Message::Move {x: 12, y: 24};
let w = Message::write(String::from("hello"));
let c = Message::ChangeColor(0, 255, 255);

m.call();
}

Option 枚举

  • 定义于标准库中
  • 在 Prelude(预导入模块)中
  • 描述了:某个值可能存在(某种类型)或不存在的情况

在 Rust 中没有 Null,在其他语言中,Null 是一个值,它表示“没有值”,一个变量可以处于两种状态:空值(null)、非空。Null 的问题在于:当你尝试像使用非 Null 值那种使用 Null 值的时候,就会引起某种错误;但是 Null 的概念还是有用的,描述了某种原因而变为无效或缺失的值。Rust 中提供了类似 Null 概念的枚举:Option<T>

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

因为 Option 枚举直接包含在 Prelude(预导入模块)中,所以可以直接使用

  • Option<T>
  • Some(T)
  • None
1
2
3
4
5
6
7
fn main() {
let some_number = Some(5);
let some_string = Some("A String");

// 因为使用 None 时,编译器无法推导 Option 里面的 T 是什么类型类型的,所以需要显式的声明
let absent_number: Option<i32> = None;
}

Option<T> VS Null

  • Option<T> 和 T 是不同的类型,不可以把 Option<T> 直接当成 T
  • 若想使用 Option<T> 中的 T,必须将它转换为 T
1
2
3
4
5
6
7
fn main() {
let x: i8 = 5;
let y: Option<i8> = Some(5);

// 不能相加,需要将 y 转为 i8 才可以
let sum = x + y;
}

match

强大的控制流运算符 - match

  • 允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码
  • 模式可以是字面值、变量名、通配符…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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,
}
}

fn main() {}

绑定值的模式

  • 匹配分支可以绑定到被匹配对象的部分值
    • 因此,可以从 enum 变体中提取值
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
32
#[derive(Debug)]
enum UsState {
Alabma,
Alaska,
}

enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("state quarter from: {:?}!", state);
25
},
}
}

fn main() {
let c = Coin::Quarter(UsState::Alaska);
println!("{}", value_in_cents(c));
}

匹配 Option<T>

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}

fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}

match 匹配的时候必须穷举所有的可能,在上述的例子中,如果在 match 中少写了 None 条件,那么代码就会报错(non-exhaustive patterns: 'Nong' not covered),所以 match 必须穷举所有可能,才能确保代码的有效。

我们可以使用 _ 通配符,来代替其余没有列出的值

1
2
3
4
5
6
7
8
9
10
fn main() {
let v = 0u8;
match v {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
}

if let

  • 处理只关心一种匹配而忽略其它匹配的情况
  • 更少的代码、更少的缩进、更少的模版代码
  • 放弃了穷举的可能
  • 可以把 if let 看作是 match 的语法糖
  • if let 也可以搭配 else 来进行使用
1
2
3
4
5
6
7
8
fn main() {
let v = Some(0u8);
if let Some(3) = v {
println!("three");
} else {
println!("others");
}
}

以上代码等价于下列的代码

1
2
3
4
5
6
7
fn main() {
let v = Some(0u8);
match v {
Some(3) => println!("three"),
_ => println!("others"),
}
}