枚举和模式匹配
枚举
枚举(enumerations),也被称作 enums,枚举允许你通过列举可能的 成员(variants)来定义一个类型
rust
//定义枚举
#[derive(Debug)]
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
println!("home is {:#?}", home);
}
//定义方法
#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
println!("{:?}", self);
}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
}
Option
枚举和其相对于空值的优势
Option
是标准库定义的另一个枚举。Option
类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。例如,如果请求一个非空列表的第一项,会得到一个值,如果请求一个空的列表,就什么也不会得到。从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况,这样就可以避免在其他编程语言中非常常见的 bug。
编程语言的设计经常要考虑包含哪些功能,但考虑排除哪些功能也很重要。Rust 并没有很多其他语言中有的空值功能。空值(Null )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。
------------------待补充
match控制流结构
Rust 有一个叫做
match
的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成,match
的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。可以把
match
表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会通过match
的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。
rust
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn main() {
print!("函数返回值是{}", value_in_cents(Coin::Penny))
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
绑定值模式
匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值。这也就是如何从枚举成员中提取值的。
rust
#[derive(Debug)] // 这样可以立刻看到州的名称
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn main() {
print!(
"函数返回值是{}",
value_in_cents(Coin::Quarter((UsState::Alaska)))
)
}
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>
rust
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("{:?}", six);//Some(6)
println!("{:?}", none);//None
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
匹配是穷尽的
rust
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("{:?}", six); //Some(6)
println!("{:?}", none); //None
}
fn plus_one(x: Option<i32>) -> Option<i32> {
//报错
// = note: the matched value is of type `Option<i32>` help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
match x {
Some(i) => Some(i + 1),
}
}
通配模式和 _
占位符
rust
fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
/* 对于前两个分支,匹配模式是字面值 3 和 7,最后一个分支则涵盖了所有其他可能的值,模式是我们命名为 other 的一个变量。other 分支的代码通过将其传递给 move_player 函数来使用这个变量。 */
// other => move_player(other),
/* 当我们不想使用通配模式获取的值时,请使用 _ ,这是一个特殊的模式,可以匹配任意值而不绑定到该值。这告诉 Rust 我们不会使用这个值,所以 Rust 也不会警告我们存在未使用的变量。 */
// _ => reroll(),
/* 在这里,我们明确告诉 Rust 我们不会使用与前面模式不匹配的值,并且这种情况下我们不想运行任何代码。 */
_ => (),
}
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
fn reroll() {}
if let
简洁控制流
if let
语法让我们以一种不那么冗长的方式结合if
和let
,来处理只匹配一个模式的值而忽略其他模式的情况。
rust
fn main() {
/* match config_max {
Some(max) => println!("The maximum is configured to be {max}"),
_ => (),
} */
/* if let 语法获取通过等号分隔的一个模式和一个表达式。它的工作方式与 match 相同,这里的表达式对应 match 而模式则对应第一个分支。在这个例子中,模式是 Some(max),max 绑定为 Some 中的值。接着可以在 if let 代码块中使用 max 了,就跟在对应的 match 分支中一样。模式不匹配时 if let 块中的代码不会执行。
使用 if let 意味着编写更少代码,更少的缩进和更少的样板代码。然而,这样会失去 match 强制要求的穷尽性检查。match 和 if let 之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。
换句话说,可以认为 if let 是 match 的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值 */
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {max}");
}
/* 可以在 if let 中包含一个 else。else 块中的代码与 match 表达式中的 _ 分支块中的代码相同,这样的 match 表达式就等同于 if let 和 else。 */
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {max}");
} else {
println!("The maximum is not configured");
}
}