Rust快速入门(4) - 枚举&模式匹配

1. 枚举的定义与使用

1.1 基本枚举

枚举(enum)允许我们定义一个类型,该类型的值只能是一组可能变体中的一个。

enum IpAddrKind {
    V4,
    V6,
}

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

fn route(ip_kind: IpAddrKind) {}

注意枚举值通过命名空间语法来访问:IpAddrKind::V4

1.2 带有数据的枚举变体

枚举变体可以携带各种类型的数据:

enum IpAddr {
    V4(String),
    V6(String),
}

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

let home = IpAddr::V4(127, 0, 0, 1);

复杂的例子:

enum Message {
    Quit,                       // 没有关联数据
    Move { x: i32, y: i32 },    // 包含匿名结构体
    Write(String),              // 包含单个String
    ChangeColor(i32, i32, i32), // 包含三个i32值
}

1.3 为枚举实现方法

可以使用impl关键字为枚举定义方法:

impl Message {
    fn call(&self) {
        // 方法体
    }
}

let m = Message::Write(String::from("hello"));
m.call();

2. Option枚举

Rust标准库定义的Option<T>枚举表示一个值可能存在(Some)或不存在(None)。

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

Option<T>已包含在预导入模块中,无需显式引入作用域,可以直接使用SomeNone

let some_number = Some(5);     // 类型为Option<i32>
let some_char = Some('e');     // 类型为Option<char>
let absent_number: Option<i32> = None; // 必须标注类型

Rust不允许将Option<T>T类型直接操作,必须先将Option<T>转换为T

let x: i8 = 5;
let y: Option<i8> = Some(5);
// 错误:不能直接相加不同类型
// let sum = x + y;

这种设计防止了空值引用错误,编译器确保我们处理了值可能不存在的情况。

3. match控制流结构

match是一个强大的控制流运算符,允许我们将一个值与一系列模式进行比较并执行相应代码。

3.1 基本语法

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

每个分支由两部分组成:模式和要执行的代码。多行代码需要用大括号:

match coin {
    Coin::Penny => {
        println!("Lucky penny!");
        1
    },
    // 其他分支...
}

3.2 绑定值的模式

模式可以绑定匹配值的部分:

#[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
        }
    }
}

3.3 匹配Option

match可以优雅地处理Option<T>

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

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

3.4 匹配必须穷尽

match表达式必须覆盖所有可能的情况,编译器会检查:

// 错误:没有处理None情况
fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        Some(i) => Some(i + 1),
        // 缺少对None的处理
    }
}

3.5 通配符模式

使用通配符_处理其他所有情况:

let dice_roll = 9;
match dice_roll {
    3 => add_fancy_hat(),
    7 => remove_fancy_hat(),
    _ => reroll(), // 处理所有其他值
}

使用_表示我们不关心这个值:

match dice_roll {
    3 => add_fancy_hat(),
    7 => remove_fancy_hat(),
    _ => (), // 不做任何操作
}

4. 简洁控制流:if let和let else

4.1 if let语法

if let提供了一种更简洁的方式来处理只关心一种匹配情况的值:

// 使用match
let config_max = Some(3u8);
match config_max {
    Some(max) => println!("最大值为 {}", max),
    _ => (),
}

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

if let可以包含else

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

4.2 let else语法

let else是处理模式不匹配情况的简洁方式,特别适用于”快乐路径”模式:

fn describe_state_quarter(coin: Coin) -> Option<String> {
    // 如果不是Quarter类型,直接返回None
    let Coin::Quarter(state) = coin else {
        return None;
    };
    
    // 主要逻辑
    if state.existed_in(1900) {
        Some(format!("{:?}对美国来说很古老!", state))
    } else {
        Some(format!("{:?}相对较新.", state))
    }
}

不同于if letlet else要求else分支必须从函数返回(或跳出当前作用域)。

总结

枚举和模式匹配是Rust的强大功能:

  1. 枚举允许定义可以是一组指定变体之一的类型,变体可以携带不同类型和数量的数据
  2. Option枚举表示可选值,帮助避免空值错误
  3. match表达式允许根据枚举的变体执行不同代码,必须处理所有可能情况
  4. if letlet else提供了更简洁的方式处理特定模式匹配

这些功能使Rust能够在类型系统层面防止错误,并提供清晰、可读的代码结构。


好好学习,天天向上