Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

枚举

如果说结构体(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
数据访问通过 . 直接访问字段必须通过 matchif let 解构
主要用途定义具体的数据实体定义状态机、分类、错误处理