6. Rust 枚举与模式匹配
6.1 定义枚举
枚举(Enum)是一种类型,它允许你定义一个类型可以是多个不同的变体之一。枚举的变体称为 枚举成员(Enum Member)。
enum IpAddrKind {
V4,
V6,
}创建枚举实例:
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;枚举不同的变体可以包含不同的数据类型和关联数据。例如:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}使用:
#[derive(Debug)]
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"));
println!("home: {:?}", home);
println!("loopback: {:?}", loopback);
}还可以为枚举实现方法:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// method body would be defined here
println!("call")
}
}
fn main() {
let msg = Message::Write(String::from("hello"));
let msg2 = Message::ChangeColor(0, 0, 0);
let msg3 = Message::Move { x: 0, y: 0 };
let msg4 = Message::Quit;
msg.call();
}其中 Move 成员包含了一个匿名结构体,Write 成员包含了一个 String,ChangeColor 成员包含了三个 i32 值。
6.2 Option 枚举
Option 是标准库中定义的一个枚举,它有两个变体:Some 和 None。其位于预导入库中,可直接使用。
Rust 中没有 Null 值,Rust 提供了 Option<T> 来处理可能为 null 的情况。其定义如下:
enum Option<T> {
Some(T),
None,
}使用:
fn main() {
let some_number = Some(5); // Some(i32)
let some_string = Some("a string"); // Some(&str)
let absent_number: Option<i32> = None; // None
}Option<T> 和 T 是不同的类型,我们使用时必须将其转换为相同的类型。可以使用 unwrap 方法来获取 Option<T> 中的值,如果 Option<T> 是 None,unwrap 会导致 panic。
fn main() {
let some_number = Some(5);
let number = 5;
let sum = number + some_number.unwrap_or(0);
println!("sum: {}", sum);
}6.3 match 控制流
match 是 Rust 中的一个关键字,它允许我们将一个值与一系列模式进行比较,并根据匹配的模式执行相应的代码。
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,
}
}绑定值的模式:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
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
}
}
}
fn main() {
let c = Coin::Quarter(UsState::Alaska);
println!("{}", value_in_cents(c));
}同理,Option<T> 也可以使用 match:
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("{:?}", six);
println!("{:?}", none);
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}match 匹配必须穷举所有可能的情况,否则编译器会报错。可以使用 _ 通配符来匹配所有其他情况。
fn main() {
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
}6.4 if let
if let 语法允许我们只匹配一个模式,而忽略其他模式。if let 语法适用于只关心一个模式的情况,而不关心其他情况。
fn main() {
let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
println!("three");
}
}if let 表达式可以和 else 一起使用:
fn main() {
let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
println!("three");
} else {
println!("not three");
}
}这类似于 match 语句,但是更简洁。
6.5 枚举的高级用法
枚举中的数据
枚举可以携带不同类型和数量的数据:
enum Message {
Quit, // 没有关联数据
Move { x: i32, y: i32 }, // 命名字段
Write(String), // 单个字符串
ChangeColor(i32, i32, i32), // 三个整数
}
impl Message {
fn process(&self) {
match self {
Message::Quit => println!("退出"),
Message::Move { x, y } => println!("移动到 ({}, {})", x, y),
Message::Write(text) => println!("写入: {}", text),
Message::ChangeColor(r, g, b) => println!("改变颜色为 RGB({}, {}, {})", r, g, b),
}
}
}
fn main() {
let messages = vec![
Message::Quit,
Message::Move { x: 10, y: 20 },
Message::Write(String::from("Hello")),
Message::ChangeColor(255, 0, 0),
];
for msg in messages {
msg.process();
}
}Result 枚举
Result 是 Rust 标准库中用于错误处理的枚举:
enum Result<T, E> {
Ok(T),
Err(E),
}使用示例:
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("创建文件时出错: {:?}", e),
},
other_error => panic!("打开文件时出错: {:?}", other_error),
},
};
}更简洁的写法:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == std::io::ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("创建文件失败: {:?}", error);
})
} else {
panic!("打开文件失败: {:?}", error);
}
});
}使用 ? 运算符
? 运算符可以简化错误传播:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("username.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}? 运算符只能用于返回 Result 或 Option 的函数。
match 的其他模式
匹配多个值
fn main() {
let x = 1;
match x {
1 | 2 => println!("一或二"),
3 => println!("三"),
_ => println!("其他"),
}
}匹配范围
fn main() {
let x = 5;
match x {
1..=5 => println!("1 到 5"),
6..=10 => println!("6 到 10"),
_ => println!("其他"),
}
}解构匹配
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x: 0, y } => println!("在 y 轴上,y = {}", y),
Point { x, y: 0 } => println!("在 x 轴上,x = {}", x),
Point { x, y } => println!("不在轴上,({}, {})", x, y),
}
}忽略部分值
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("第一个: {}, 最后一个: {}", first, last);
}
}
}匹配守卫
fn main() {
let num = Some(4);
match num {
Some(x) if x < 5 => println!("小于 5: {}", x),
Some(x) => println!("{}", x),
None => (),
}
}@ 绑定
enum Message {
Hello { id: i32 },
}
fn main() {
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello { id: id_variable @ 3..=7 } => {
println!("找到了一个在范围内的 id: {}", id_variable)
}
Message::Hello { id: 10..=12 } => {
println!("找到了另一个范围内的 id")
}
Message::Hello { id } => {
println!("找到了其他 id: {}", id)
}
}
}实用示例:构建状态机
#[derive(Debug)]
enum TrafficLight {
Red,
Yellow,
Green,
}
impl TrafficLight {
fn next(&self) -> TrafficLight {
match self {
TrafficLight::Red => TrafficLight::Green,
TrafficLight::Yellow => TrafficLight::Red,
TrafficLight::Green => TrafficLight::Yellow,
}
}
fn duration(&self) -> u32 {
match self {
TrafficLight::Red => 60,
TrafficLight::Yellow => 3,
TrafficLight::Green => 45,
}
}
}
fn main() {
let mut light = TrafficLight::Red;
for _ in 0..5 {
println!("{:?} 持续 {} 秒", light, light.duration());
light = light.next();
}
}Option 的常用方法
Option<T> 提供了许多有用的方法:
fn main() {
let some_number = Some(5);
let no_number: Option<i32> = None;
// is_some() 和 is_none()
println!("some_number is some: {}", some_number.is_some());
println!("no_number is none: {}", no_number.is_none());
// unwrap_or()
println!("{}", some_number.unwrap_or(0));
println!("{}", no_number.unwrap_or(0));
// map()
let squared = some_number.map(|x| x * x);
println!("{:?}", squared);
// and_then()
let result = some_number.and_then(|x| {
if x > 0 {
Some(x * 2)
} else {
None
}
});
println!("{:?}", result);
// filter()
let filtered = some_number.filter(|&x| x > 3);
println!("{:?}", filtered);
}通过这些高级用法,我们可以看到枚举在 Rust 中的强大功能。枚举不仅可以表示不同的状态,还可以携带数据,配合模式匹配,可以写出非常清晰和安全的代码。