跳到主要内容

枚举(Enum)

枚举(Enumerations),也被称作 Enum,允许你通过列举可能的成员(variants)来定义一个类型。

定义枚举

让我们看看一个我们可能要用代码表达的场景:IP 地址。目前被广泛使用的两个主要 IP 标准:IPv4(version four)和 IPv6(version six)。

enum IpAddrKind {
V4,
V6,
}

fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

route(IpAddrKind::V4);
route(IpAddrKind::V6);
}

fn route(ip_kind: IpAddrKind) {
// 处理 IP 地址
}

枚举值

我们可以像这样创建 IpAddrKind 两个不同成员的实例:

enum IpAddrKind {
V4,
V6,
}

struct IpAddr {
kind: IpAddrKind,
address: String,
}

fn main() {
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),
}

fn main() {
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}

用枚举替代结构体还有另一个优势:每个成员可以处理不同类型和数量的数据:

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"));
}

复杂的枚举示例

让我们看一个更复杂的枚举例子:

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

fn main() {
let m1 = Message::Quit;
let m2 = Message::Move { x: 10, y: 20 };
let m3 = Message::Write(String::from("hello"));
let m4 = Message::ChangeColor(255, 0, 0);
}

这个枚举有四个含有不同类型的成员:

  • Quit 没有关联任何数据
  • Move 类似结构体包含命名字段
  • Write 包含单独一个 String
  • ChangeColor 包含三个 i32

为枚举定义方法

就像可以使用 impl 来为结构体定义方法那样,也可以在枚举上定义方法:

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

impl Message {
fn call(&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 m = Message::Write(String::from("hello"));
m.call();
}

Option 枚举

Option 是标准库定义的另一个枚举。Option 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值:

enum Option<T> {
None,
Some(T),
}

fn main() {
let some_number = Some(5);
let some_char = Some('e');

let absent_number: Option<i32> = None;

println!("{:?}", some_number);
println!("{:?}", some_char);
println!("{:?}", absent_number);
}

使用 Option

fn main() {
let x: i8 = 5;
let y: Option<i8> = Some(5);

// let sum = x + y; // 这会编译错误!

// 正确的做法
let sum = x + y.unwrap_or(0);
println!("Sum: {}", sum);

// 更安全的做法
match y {
Some(value) => println!("Sum: {}", x + value),
None => println!("y 没有值"),
}
}

match 控制流结构

Rust 有一个叫做 match 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码:

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,
}
}

fn main() {
let coin = Coin::Quarter;
println!("硬币价值: {} 美分", value_in_cents(coin));
}

绑定值的模式

匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值:

#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}

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!("来自 {:?} 州的 25 美分硬币!", state);
25
}
}
}

fn main() {
let coin = Coin::Quarter(UsState::Alaska);
println!("硬币价值: {} 美分", value_in_cents(coin));
}

匹配 Option

fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}

fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);

println!("{:?}", six);
println!("{:?}", none);
}

通配模式和 _ 占位符

对于枚举,当我们不想列举出所有可能的值时,可以使用特殊的模式 _

fn main() {
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() {}

if let 简洁控制流

if let 语法让我们以一种不那么冗长的方式结合 iflet,来处理只匹配一个模式的值而忽略其他模式的情况:

fn main() {
let config_max = Some(3u8);

// 使用 match
match config_max {
Some(max) => println!("最大值是 {}", max),
_ => (),
}

// 使用 if let(更简洁)
if let Some(max) = config_max {
println!("最大值是 {}", max);
}
}

if let 与 else

enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}

#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}

fn main() {
let coin = Coin::Penny;
let mut count = 0;

if let Coin::Quarter(state) = coin {
println!("来自 {:?} 州的 25 美分硬币!", state);
} else {
count += 1;
}

println!("非 25 美分硬币数量: {}", count);
}

实际应用示例

状态机

#[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 => 10,
TrafficLight::Green => 45,
}
}
}

fn main() {
let mut light = TrafficLight::Red;

for _ in 0..5 {
println!("当前灯: {:?}, 持续时间: {} 秒", light, light.duration());
light = light.next();
}
}

错误处理

#[derive(Debug)]
enum MathError {
DivisionByZero,
NegativeSquareRoot,
}

fn divide(x: f64, y: f64) -> Result<f64, MathError> {
if y == 0.0 {
Err(MathError::DivisionByZero)
} else {
Ok(x / y)
}
}

fn sqrt(x: f64) -> Result<f64, MathError> {
if x < 0.0 {
Err(MathError::NegativeSquareRoot)
} else {
Ok(x.sqrt())
}
}

fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("10 / 2 = {}", result),
Err(error) => println!("错误: {:?}", error),
}

match sqrt(-4.0) {
Ok(result) => println!("√(-4) = {}", result),
Err(error) => println!("错误: {:?}", error),
}
}

配置选项

#[derive(Debug)]
enum Theme {
Light,
Dark,
Auto,
}

#[derive(Debug)]
enum Language {
English,
Chinese,
Spanish,
}

#[derive(Debug)]
struct Config {
theme: Theme,
language: Language,
notifications: bool,
}

impl Config {
fn new() -> Config {
Config {
theme: Theme::Auto,
language: Language::English,
notifications: true,
}
}

fn set_theme(&mut self, theme: Theme) {
self.theme = theme;
}

fn apply(&self) {
match self.theme {
Theme::Light => println!("应用浅色主题"),
Theme::Dark => println!("应用深色主题"),
Theme::Auto => println!("应用自动主题"),
}

match self.language {
Language::English => println!("设置语言为英语"),
Language::Chinese => println!("设置语言为中文"),
Language::Spanish => println!("设置语言为西班牙语"),
}

if self.notifications {
println!("启用通知");
} else {
println!("禁用通知");
}
}
}

fn main() {
let mut config = Config::new();
config.set_theme(Theme::Dark);

println!("配置: {:?}", config);
config.apply();
}

最佳实践

  1. 使用有意义的枚举名和成员名:清楚地表达其用途
  2. 合理使用数据关联:根据需要为枚举成员附加数据
  3. 实现常用方法:为枚举添加有用的方法
  4. 优先使用 match:处理所有可能的情况
  5. 适当使用 if let:当只关心一种情况时
#[derive(Debug, Clone, PartialEq)]
enum Status {
Pending,
InProgress { started_at: String },
Completed { finished_at: String },
Failed { error: String },
}

impl Status {
fn is_finished(&self) -> bool {
matches!(self, Status::Completed { .. } | Status::Failed { .. })
}

fn description(&self) -> &str {
match self {
Status::Pending => "等待中",
Status::InProgress { .. } => "进行中",
Status::Completed { .. } => "已完成",
Status::Failed { .. } => "已失败",
}
}
}

fn main() {
let status = Status::InProgress {
started_at: "2023-01-01 10:00:00".to_string(),
};

println!("状态: {}", status.description());
println!("是否完成: {}", status.is_finished());
}