从ip地址入手学枚举
枚举的每个成员可以处理不同类型和数量的数据
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};
enum IpAddr {
V4(String),
V6(String),
}
// 直接将数据附加到枚举的每个成员上
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
使用枚举来存储两种不同 IP 地址的几种可能的选择
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
可以将任意类型的数据放入枚举成员中,甚至枚举中放枚举
虽然标准库中包含一个 IpAddr 的定义,仍然可以创建和使用自己的定义而不会有冲突,因为并没有将标准库中的定义引入作用域
struct Ipv4Addr {
// --snip--
}
struct Ipv6Addr {
// --snip--
}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
枚举实现
枚举是单独一个类型
enum Message {
Quit, // 没有关联任何数据
Move { x: i32, y: i32 }, // 包含一个匿名结构体
Write(String),
ChangeColor(i32, i32, i32),
}
结构体实现
结构体是各是各的类型
struct QuitMessage; // 类单元结构体
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String); // 元组结构体
struct ChangeColorMessage(i32, i32, i32); // 元组结构体
和结构体的相似点:在枚举上定义方法
impl Message {
fn call(&self) {
// 在这里定义方法体
}
}
let m = Message::Write(String::from("hello"));
m.call();
标准库定义的枚举
它编码了一个非常普遍的场景,即一个值要么有值要么没值
空值是一个因为某种原因目前无效或缺失的值
Rust 没有空值功能
空值带来的问题:像一个非空值那样使用一个空值,出现错误
Rust的设计:一个可以编码存在或不存在概念的枚举
它被包含在 prelude 之中,不需要将其显式引入作用域
它的成员也可以不需要 Option:: 前缀来直接使用 Some 和 None
// 是泛型
// 意味着 Option 枚举的 Some 成员可以包含任意类型的数据
enum Option {
Some(T),
None,
}
let some_number = Some(5);
let some_string = Some("a string");
// 如果使用 None,需要告诉 Rust Option 是什么类型
// 编译器只通过 None 值无法推断出 Some 成员保存的值的类型
let absent_number: Option = None;
Option 为什么就比空值要好呢?
Option 和 T(这里 T 可以是任何类型)是不同的类型
编译器不允许像一个肯定有效的值那样使用 Option
当在 Rust 中拥有一个像 i8 这样类型的值时,编译器确保它总是有一个有效的值。可以自信使用而无需做空值检查。
只有当使用 Option(或者任何用到的类型)的时候需要担心可能没有值,而编译器会确保在使用值之前处理了为空的情况。
在对 Option 进行 T 的运算之前必须将其转换为 T,通常这能捕获到空值最常见的问题之一:假设某值不为空但实际上为空的情况
let x: i8 = 5;
let y: Option = Some(5);
let sum = x + y;
为了拥有一个可能为空的值,必须要显式的将其放入对应类型的 Option 中。当使用这个值时,必须明确地处理值为空的情况。
为了使用 Option 值,需要编写处理每个成员的代码。
想要一些代码只当拥有 Some(T) 值时运行,允许这些代码使用其中的 T。希望一些代码在值为 None 时运行,这些代码并没有一个可用的 T 值。
match 表达式就是这么一个处理枚举的控制流结构:
它会根据枚举的成员运行不同的代码,
这些代码可以使用匹配到的值中的数据
将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码
模式可由字面量、变量、通配符和许多其他内容构成
#[derive(Debug)] // 这样可以立刻看到州的名称
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
// 当 match 表达式执行时,它将结果值按顺序与每一个分支的模式相比较。
// 如果模式匹配了这个值,这个模式相关联的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支
fn value_in_cents(coin: Coin) -> u8 {
// 在分支中运行多行代码,可以使用大括号
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
// 可以绑定匹配的模式的部分值。这也就是如何从枚举成员中提取值
// 调用 value_in_cents(Coin::Quarter(UsState::Alaska)),state 绑定的将会是值 UsState::Alaska
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
fn plus_one(x: Option) -> Option {
match x {
// 匹配是有穷的,如果下面二缺一,则编译不过,Rust 知道哪些模式被忘记了
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five); // 匹配Some(i) => Some(i + 1),
let none = plus_one(None) // 匹配None => None
想使用通配模式获取的值
let dice_roll = 9;
match dice_roll {
// 对一些特定的值采取特殊操作,而对其他的值采取默认操作
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
// 通配模式满足了 match 必须被穷尽的要求
other => move_player(other), // 通配所有其他模式
// 必须将通配分支放在最后,因为模式是按顺序匹配的。如果在通配分支后添加其他分支,则永远也不会被执行
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
不想使用通配模式获取的值
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(), // 占位
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn reroll() {}
可以使用单元值(空元组)作为 _ 分支的代码
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => (),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (), // 如果只想处理 Some(3),但是不处理其他的,则代码中太多冗余这种行
}
失去 match 强制要求的穷尽性检
if let Some(3) = some_u8_value {
println!("three");
}
// 如下两个示例等价
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {:?}!", state),
_ => count += 1,
}
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
当枚举值包含数据时,可以根据需要处理多少情况来选择使用 match 或 if let 来获取并使用。