Skip to content

Rust - 枚举

枚举(Enumerations),或简称为 enum,是 Rust 中的核心特性,它允许你通过列出其可能的变体(variants)来定义一个类型。当你有一个值只能是特定可能性集合中的一个时,enum 是一个完美的工具。

enum WebEvent {
// 单元类型变体
PageLoad,
// 元组结构体变体
KeyPress(char),
// 结构体变体
Click { x: i64, y: i64 },
}

在这里,WebEvent 是一个单一类型,它可以是三种不同变体中的任意一种。这种方法比定义多个单独的结构体(struct)更简洁且类型安全。

我们来创建 WebEvent 变体的实例。为了使我们的自定义类型可打印(用于调试),我们可以添加 #[derive(Debug)] 属性。这会自动实现 Debug trait(特性),允许我们在 println! 中使用 {:?} 格式化指定符。

#[derive(Debug)]
enum WebEvent {
PageLoad,
KeyPress(char),
Click { x: i64, y: i64 },
}
fn main() {
let load = WebEvent::PageLoad;
let key = WebEvent::KeyPress('k');
let click = WebEvent::Click { x: 20, y: 80 };
println!("{:?}", load);
println!("{:?}", key);
println!("{:?}", click);
}
PageLoad
KeyPress('k')
Click { x: 20, y: 80 }

Rust 有一个强大的控制流表达式 match,它允许你将一个值与一系列模式进行比较,然后根据匹配到的模式执行代码。枚举和 match 是完美的组合。

match 的一个关键优势是它是穷尽的(exhaustive)。编译器会检查你是否覆盖了枚举的所有可能变体。如果你遗漏了一个,你的代码将无法编译,从而防止了一整类 bug。

fn inspect_event(event: WebEvent) {
match event {
WebEvent::PageLoad => println!("Page loaded"),
WebEvent::KeyPress(c) => println!("Pressed '{}'.", c),
WebEvent::Click { x, y } => {
println!("Clicked at x={}, y={}.", x, y);
}
}
}
fn main() {
let load = WebEvent::PageLoad;
let key = WebEvent::KeyPress('k');
let click = WebEvent::Click { x: 20, y: 80 };
inspect_event(load);
inspect_event(key);
inspect_event(click);
}
Page loaded
Pressed 'k'.
Clicked at x=20, y=80.

Rust 没有 null。相反,它有一个标准库枚举 Option<T>,它编码了值存在或缺失的概念。这是一个巨大的优势,因为它强制你在编译时处理“空”情况,从而防止了一大类 bug。

enum Option<T> {
Some(T), // 表示存在一个类型为 T 的值
None, // 表示值的缺失
}

我们通过一个可能不返回值的函数来看看它的实际应用。因为 Option<T> 是一个枚举,编译器会确保你处理 None 的情况,使你的代码更加健壮。

fn find_user(name: &str) -> Option<u32> {
match name {
"Alice" => Some(101),
"Bob" => Some(102),
_ => None,
}
}
fn main() {
let user_id = find_user("Alice");
match user_id {
Some(id) => println!("Found user ID: {}", id),
None => println!("User not found."),
}
}
Found user ID: 101

有时,如果你只关心一个特定变体,match 表达式会显得过于冗长。对于这些情况,Rust 提供了 if let 和 while let 作为更简洁的语法。

fn main() {
let maybe_number: Option<i32> = Some(7);
// 使用 `if let` 比单臂的 `match` 更简洁。
if let Some(number) = maybe_number {
println!("The number is: {}", number);
} else {
println!("There is no number.");
}
// `while let` 循环,只要模式匹配就一直执行。
let mut optional = Some(0);
while let Some(i) = optional {
if i > 5 {
println!("Greater than 5, quitting.");
optional = None;
} else {
println!("`i` is `{:?}`. Try again.", i);
optional = Some(i + 1);
}
}
}

if let Some(number) = maybe_number 可以读作:“如果 maybe_number 是一个 Some 值,就将其内部值绑定到 number 变量,供 if 代码块使用。”

  • 拥抱 match 和 if let:始终使用这些构造来处理 Option 和其他枚举。避免使用 .unwrap() 或 .expect(),这可能导致你的程序 panic(崩溃)。只有当你绝对确定某个值将是 Some 且 None 表示一个关键的、无法恢复的 bug 时,才使用 .expect("message")。
  • 使用枚举来建模状态:枚举非常适合创建状态机。例如,enum PageState { Loading, Loaded(Content), Error(String) }。这使得代码中不可能表示无效状态。
  • 保持变体的一致性:枚举的变体应代表一个单一、统一概念的不同状态。

与 Option 类似,Result<T, E> 枚举是处理可能成功或失败的操作的标准方式。它被定义为 enum Result<T, E> { Ok(T), Err(E) }。你会在 Rust 的各个地方遇到它,尤其是在 I/O 和解析操作中。我们将在错误处理章节中更详细地介绍它。