枚举
如果说结构体(Struct)是将多个相关数据“打包”在一起,那么枚举Enums则是让一个变量在“多种可能”中选择其一。
在 Rust 中,枚举不仅是其他语言中常见的整数常量列表,它还是功能极其强大的 代数数据类型(Algebraic Data Types) 。
一、 基础枚举:简单的分类
这是枚举最基础的用法,用于定义一组离散的选项。
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
// 枚举可以作为函数参数
route(four);
route(six);
}
fn route(ip_kind: IpAddrKind) {}
关键点:
- 枚举变体(如 V4、V6)是枚举类型的成员。
- 枚举可以作为参数传递给函数,也可以在函数中返回。
- 你可以在
match表达式中匹配枚举的每个变体,处理不同的情况。
二、 枚举的真威力:携带数据
在 Rust 中,每个枚举变体(Variant)都可以关联不同类型、不同数量的数据。这让你可以用一个类型表达多种结构完全不同的信息。
enum Message {
Quit, // 无数据
Move { x: i32, y: i32 }, // 匿名结构体
Write(String), // 单个 String
ChangeColor(i32, i32, i32), // 元组
}
impl Message {
fn call(&self) {
// 你也可以为枚举定义方法!
}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
}
为什么这比结构体好用?
如果你用结构体来实现上面的功能,你可能需要定义 4 个不同的结构体(Rust 是一种强静态类型语言,函数在编译时必须明确知道它接收的参数是什么类型,以及该类型占用的空间大小)。而使用枚举,它们都属于 Message 类型,方便在函数间统一传递。
三、 核心中的核心:Option 枚举
Rust 没有空值(Null) 。为了表达“一个值可能不存在”,Rust 使用了标准库中定义的 Option<T> 枚举:
enum Option<T> {
None,
Some(T),
}
Some(T):代表有值,值为T类型。None:代表没有值。
意义何在?
在有 Null 的语言中,你随时可能忘记检查空指针而导致崩溃。在 Rust 中,如果你有一个 Option <i32>,你必须处理 None 的情况,否则代码编译不通过。这从根本上杜绝了空指针异常。
四、 模式匹配:枚举的完美搭档
要获取枚举内部的数据,最常用的工具就是 match 表达式。
1. match:穷尽式检查
match 强制你处理枚举的每一个变体。
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);
let cents = value_in_cents(coin);
println!("{} 美分", cents);
}
2. if let:更简洁的匹配
如果你只关心其中的一种情况,if let 是比 match 更优雅的选择。
fn main() {
let some_u8_value = Some(3u8);
// 仅在值为 Some 时处理
if let Some(value) = some_u8_value {
println!("找到了{}!", value);
}else{
println!("没有找到值!");
}
}
五、 枚举的内存布局(进阶)
枚举在内存中是如何存储的?
Rust 会为枚举分配足够的空间来容纳最大的那个变体,此外还需要一个小的标签Tag来记录当前存的是哪一个变体。
对于一个枚举 $E$,其占用内存大小大致为:
$$ Size(E) = Size(Tag) + \max(Size(Variant_1), Size(Variant_2), \dots) $$
小技巧:
对于 Option<&T>,因为引用(指针)永远不会为 0,Rust 会非常聪明地用 0 来表示 None。这意味着 Option<&T> 和 &T 占用的空间是一样大的!
总结:结构体 vs 枚举
| 特性 | 结构体 (Struct) | 枚举 (Enum) |
|---|---|---|
| 逻辑关系 | “和”(And):包含 A 且包含 B | “或”(Or):要么是 A 要么是 B |
| 数据访问 | 通过 . 直接访问字段 | 必须通过 match 或 if let 解构 |
| 主要用途 | 定义具体的数据实体 | 定义状态机、分类、错误处理 |