五、枚举和模式匹配
1、枚举
枚举定义
enum IpAddrKind {
V4,
V6,
}
创建实例:
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
函数签名的参数为枚举类型,那该枚举类型的成员都能传入:
fn route(ip_type: IpAddrKind) { }
route(IpAddrKind::V4);
route(IpAddrKind::V6);
枚举类型可以作为结构体的字段,同时,枚举成员也可以是其他类型(包括结构体)。
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
可以看到标准库也是类似的定义方式:
struct Ipv4Addr {
// --snip--
}
struct Ipv6Addr {
// --snip--
}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
枚举类型同样可以通过 impl 定义方法
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// 在这里定义方法体
}
}
let m = Message::Write(String::from("hello"));
m.call();
标准库 Option 枚举
Option 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。
Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是 Option<T>
,而且它定义于标准库中,如下:
enum Option<T> {
Some(T),
None,
}
Option 的成员不需要 Option::
前缀就可以直接使用 。
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
如果使用 None
而不是 Some
,需要告诉 Rust Option<T>
是什么类型的,因为编译器只通过 None
值无法推断出 Some
成员保存的值的类型。
因为 Option<T>
和 T
(这里 T
可以是任何类型)是不同的类型,编译器不允许像一个肯定有效的值那样使用 Option<T>
。例如,这段代码不能编译,因为它尝试将 Option<i8>
与 i8
相加:
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
在对 Option<T>
进行 T
的运算之前必须将其转换为 T
为了拥有一个可能为空的值,你必须要显式的将其放入对应类型的 Option<T>
中。接着,当使用这个值时,必须明确的处理值为空的情况。只要一个值不是 Option<T>
类型,你就 可以 安全的认定它的值不为空。
2、match 控制流运算
定义
match
是极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
绑定值的模式
从枚举成员中提取值
#[derive(Debug)] // 这样可以立刻看到州的名称
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
Coin
中有一种 25 美分硬币 Quarter
分别有50个州的不同版本,通过 match
可以提取出州的值。
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
},
}
}
匹配Option<T>
fn main() {
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);
}
匹配是穷尽的
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
这段代码编译无法通过,因为没有考虑所有情况。
_通配符
Rust 也提供了一个模式用于不想列举出所有可能值的场景:
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
3、if let 简单控制流
只想匹配一种情况时,可以使用 if let
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
if let Some(3) = some_u8_value {
println!("three");
}else {
println!("others")
}
但是使用了 if let,就忽略了其他可能性,放弃了穷举。