Rust 枚举与模式匹配

0 阅读17分钟

主题: - 枚举与模式匹配


第一层:原理层(Why & How)

为什么需要枚举?

在编程中,我们经常需要表示一组有限的、相关的值。例如:

  • 订单状态:待支付、已支付、已发货、已完成
  • 颜色:红、绿、蓝
  • IP 地址类型:IPv4 或 IPv6

传统方式的问题

// 使用常量 - 类型不安全,容易混淆
const PENDING: u8 = 0;
const PAID: u8 = 1;
const SHIPPED: u8 = 2;

fn process_order(status: u8) {
    // 可以传入任意 u8 值,包括无效的 100
    if status == PENDING { /* ... */ }
}

枚举解决方案

enum OrderStatus {
    Pending,
    Paid,
    Shipped,
    Completed,
}

fn process_order(status: OrderStatus) {
    // 只能传入 OrderStatus 类型的值
    match status {
        OrderStatus::Pending => /* ... */,
        OrderStatus::Paid => /* ... */,
        // 编译器会检查是否覆盖所有变体
    }
}

Rust 枚举的独特之处

Rust 的枚举(enum)与其他语言有本质区别:

特性Rust 枚举C/C++ 枚举Java 枚举
变体数据每个变体可携带不同类型和数量的数据仅整数常量可携带数据,但类型受限
代数数据类型是(Algebraic Data Type)部分支持
内存布局标签 + 联合体(tagged union)整数对象引用
模式匹配原生支持,编译器检查穷尽性需手动 switch支持 switch

代数数据类型(ADT)概念

代数数据类型 = 乘积类型(Product Type)+ 和类型(Sum Type)

乘积类型(Product Type):同时包含多个值
- 结构体:struct Point { x: i32, y: i32 } 包含 x AND y
- 内存:x 和 y 同时存在

和类型(Sum Type):可能是多个值中的一个
- 枚举:enum Shape { Circle(f64), Rectangle(f64, f64) }
- 内存:要么是 Circle,要么是 Rectangle,不会同时存在

枚举的内存布局

enum Message {
    Quit,                           // 无数据
    Move { x: i32, y: i32 },        // 匿名结构体
    Write(String),                  // 单个值
    ChangeColor(u8, u8, u8),        // 多个值
}

内存表示

┌─────────────────────────────────────────────────────┐
│                    Message 枚举                      │
├─────────────────────────────────────────────────────┤
│  标签(discriminant)                                │
│  ├── 0: Quit                                        │
│  ├── 1: Move { x, y }                               │
│  ├── 2: Write(String)                               │
│  └── 3: ChangeColor(u8, u8, u8)                     │
├─────────────────────────────────────────────────────┤
│  数据(联合体)                                      │
│  ├── Quit:    [空]                                  │
│  ├── Move:    [x: i32, y: i32]     (8 bytes)        │
│  ├── Write:   [String 结构体]       (24 bytes)      │
│  └── ChangeColor: [u8, u8, u8, pad] (4 bytes)       │
├─────────────────────────────────────────────────────┤
│  总大小 = max(变体大小) + 标签大小                   │
│  Message 大小 = 24 (String) + 1 (标签+对齐) = 32    │
└─────────────────────────────────────────────────────┘

内存优化 - 空指针优化(NPO)

enum Option<&T> {
    Some(&T),    // 非空指针
    None,        // 空指针
}

// 优化后:Option<&T> 大小与 &T 相同!
// 使用空指针表示 None,无需额外标签

Option 枚举的设计哲学

Rust 没有空指针(null),使用 Option<T> 显式处理可能缺失的值:

enum Option<T> {
    None,      // 值不存在
    Some(T),   // 值存在
}

与 null 的对比

其他语言(Java/C++):
String s = null;           // 可以赋值为 null
s.length();                // 运行时崩溃!NullPointerException

Rust:
let s: Option<String> = None;  // 显式标记可能为空
s.len();                       // 编译错误!不能直接在 Option 上调用

// 正确处理
match s {
    Some(ref str) => println!("{}", str.len()),
    None => println!("No string"),
}

哲学:将运行时错误转为编译时错误

Result 枚举的错误处理

enum Result<T, E> {
    Ok(T),    // 操作成功,包含返回值
    Err(E),   // 操作失败,包含错误信息
}

与异常机制的对比

特性Rust ResultJava/C++ 异常Go 多返回值
显式性必须处理可忽略可忽略
性能零成本抽象有开销(栈展开)零成本
类型安全编译时检查运行时捕获无类型约束
组合能力强大的组合子(map/and_then)有限手动检查

模式匹配的本质

模式匹配是 Rust 的核心特性,它不仅仅是 switch 语句的增强:

match value {
    pattern1 => expression1,
    pattern2 => expression2,
    _ => default_expression,
}

匹配原理

┌─────────────────────────────────────────────┐
│              模式匹配过程                    │
├─────────────────────────────────────────────┤
│  1. 计算 value 的值                          │
│  2. 按顺序匹配 pattern                       │
│  3. 第一个匹配的 pattern 被执行              │
│  4. 绑定 pattern 中的变量                    │
│  5. 执行对应的 expression                    │
└─────────────────────────────────────────────┘

穷尽性检查(Exhaustiveness Checking)

enum Color {
    Red,
    Green,
    Blue,
}

fn describe(c: Color) -> &'static str {
    match c {
        Color::Red => "红色",
        Color::Green => "绿色",
        // 编译错误!缺少 Color::Blue 的处理
    }
}

编译器会确保 match 覆盖所有可能的情况,防止遗漏。


第二层:实战层(What & Do)

1. 定义和使用枚举

基础枚举

// 定义枚举
enum Direction {
    North,
    South,
    East,
    West,
}

fn main() {
    // 创建枚举值
    let dir = Direction::North;
    
    // 使用 match 处理
    match dir {
        Direction::North => println!("向北走"),
        Direction::South => println!("向南走"),
        Direction::East => println!("向东走"),
        Direction::West => println!("向西走"),
    }
}

带数据的枚举

enum Message {
    // 无数据
    Quit,
    
    // 匿名结构体
    Move { x: i32, y: i32 },
    
    // 单个值
    Write(String),
    
    // 多个值
    ChangeColor(u8, u8, u8),
}

fn process_message(msg: Message) {
    match msg {
        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 msg1 = Message::Quit;
    let msg2 = Message::Move { x: 10, y: 20 };
    let msg3 = Message::Write(String::from("Hello"));
    let msg4 = Message::ChangeColor(255, 0, 0);
    
    process_message(msg1);
    process_message(msg2);
    process_message(msg3);
    process_message(msg4);
}

为枚举定义方法

enum Shape {
    Circle(f64),           // 半径
    Rectangle(f64, f64),   // 宽, 高
    Triangle(f64, f64, f64), // 三边
}

impl Shape {
    // 计算面积
    fn area(&self) -> f64 {
        match self {
            Shape::Circle(r) => std::f64::consts::PI * r * r,
            Shape::Rectangle(w, h) => w * h,
            Shape::Triangle(a, b, c) => {
                // 海伦公式
                let s = (a + b + c) / 2.0;
                (s * (s - a) * (s - b) * (s - c)).sqrt()
            }
        }
    }
    
    // 判断是否是圆形
    fn is_circle(&self) -> bool {
        matches!(self, Shape::Circle(_))
    }
}

fn main() {
    let circle = Shape::Circle(5.0);
    let rect = Shape::Rectangle(4.0, 5.0);
    
    println!("圆面积: {:.2}", circle.area());
    println!("矩形面积: {:.2}", rect.area());
    println!("是圆形吗? {}", circle.is_circle());
}

2. Option 枚举实战

基本使用

fn find_char(s: &str, c: char) -> Option<usize> {
    for (i, ch) in s.chars().enumerate() {
        if ch == c {
            return Some(i);
        }
    }
    None
}

fn main() {
    let s = "Hello, World!";
    
    match find_char(s, 'W') {
        Some(index) => println!("找到字符,位置: {}", index),
        None => println!("未找到字符"),
    }
    
    // 使用 if let 简化
    if let Some(index) = find_char(s, 'X') {
        println!("找到字符,位置: {}", index);
    } else {
        println!("未找到字符");
    }
}

Option 的组合方法

fn main() {
    let some_number = Some(5);
    let some_string = Some("Hello");
    let absent_number: Option<i32> = None;
    
    // map: 转换 Some 中的值
    let doubled = some_number.map(|x| x * 2);
    println!("{:?}", doubled); // Some(10)
    
    // unwrap_or: 提供默认值
    let result = absent_number.unwrap_or(0);
    println!("{}", result); // 0
    
    // unwrap_or_else: 惰性计算默认值
    let result = absent_number.unwrap_or_else(|| {
        println!("计算默认值");
        42
    });
    println!("{}", result); // 42
    
    // and_then: 链式操作(类似 flatMap)
    let result = some_number.and_then(|x| {
        if x > 0 {
            Some(x * 2)
        } else {
            None
        }
    });
    println!("{:?}", result); // Some(10)
    
    // or_else: 提供备选 Option
    let result = absent_number.or_else(|| Some(100));
    println!("{:?}", result); // Some(100)
    
    // filter: 过滤不符合条件的值
    let result = some_number.filter(|&x| x > 10);
    println!("{:?}", result); // None
    
    // take: 取出值,留下 None
    let mut some_value = Some(5);
    let taken = some_value.take();
    println!("taken: {:?}", taken);       // Some(5)
    println!("remaining: {:?}", some_value); // None
    
    // replace: 替换值,返回旧值
    let mut some_value = Some(5);
    let old = some_value.replace(10);
    println!("old: {:?}", old);           // Some(5)
    println!("new: {:?}", some_value);    // Some(10)
}

解构 Option

fn main() {
    let x = Some(5);
    let y = Some(10);
    let none: Option<i32> = None;
    
    // 解构 Some
    match x {
        Some(value) => println!("值是 {}", value),
        None => println!("没有值"),
    }
    
    // 嵌套解构
    let nested = Some(Some(42));
    match nested {
        Some(Some(v)) => println!("嵌套值: {}", v),
        Some(None) => println!("内部是 None"),
        None => println!("外部是 None"),
    }
    
    // 使用 ? 运算符(只能在返回 Option 的函数中使用)
    fn add_options(a: Option<i32>, b: Option<i32>) -> Option<i32> {
        let a_val = a?;  // 如果 a 是 None,提前返回 None
        let b_val = b?;  // 如果 b 是 None,提前返回 None
        Some(a_val + b_val)
    }
    
    println!("{:?}", add_options(x, y));   // Some(15)
    println!("{:?}", add_options(x, none)); // None
}

3. Result 枚举实战

基本使用

use std::fs::File;
use std::io::Error;

fn open_file(path: &str) -> Result<File, Error> {
    File::open(path)
}

fn main() {
    match open_file("example.txt") {
        Ok(file) => println!("文件打开成功"),
        Err(error) => println!("打开失败: {}", error),
    }
}

Result 的组合方法

use std::fs::File;
use std::io::{self, Read};

fn main() -> io::Result<()> {
    // map: 转换成功值
    let result: Result<i32, &str> = Ok(5);
    let mapped = result.map(|x| x * 2);
    println!("{:?}", mapped); // Ok(10)
    
    // map_err: 转换错误值
    let result: Result<i32, &str> = Err("error");
    let mapped_err = result.map_err(|e| format!("失败: {}", e));
    println!("{:?}", mapped_err); // Err("失败: error")
    
    // and_then: 链式操作
    let result: Result<i32, &str> = Ok(5);
    let chained = result.and_then(|x| {
        if x > 0 {
            Ok(x * 2)
        } else {
            Err("负数")
        }
    });
    println!("{:?}", chained); // Ok(10)
    
    // unwrap: 提取成功值(失败时 panic)
    // let value = Ok(5).unwrap();  // 5
    // let value = Err("error").unwrap();  // panic!
    
    // expect: 提供自定义 panic 信息
    // let value = Err("error").expect("不应该失败");  // panic!
    
    // unwrap_or: 提供默认值
    let value = Err("error").unwrap_or(0);
    println!("{}", value); // 0
    
    // unwrap_or_else: 惰性计算默认值
    let value = Err("error").unwrap_or_else(|e| {
        println!("错误: {}", e);
        42
    });
    println!("{}", value); // 42
    
    // 使用 ? 运算符(只能在返回 Result 的函数中使用)
    let mut file = File::open("example.txt")?;  // 错误时提前返回
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    
    println!("内容: {}", contents);
    Ok(())
}

自定义错误类型

use std::fmt;
use std::error::Error;

// 自定义错误类型
#[derive(Debug)]
struct MyError {
    message: String,
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "MyError: {}", self.message)
    }
}

impl Error for MyError {}

// 使用自定义错误
fn divide(a: i32, b: i32) -> Result<i32, MyError> {
    if b == 0 {
        Err(MyError {
            message: String::from("除数不能为零"),
        })
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10, 2) {
        Ok(result) => println!("结果: {}", result),
        Err(e) => println!("错误: {}", e),
    }
    
    match divide(10, 0) {
        Ok(result) => println!("结果: {}", result),
        Err(e) => println!("错误: {}", e),
    }
}

4. 模式匹配详解

match 表达式

fn main() {
    // 匹配数值
    let number = 13;
    match number {
        1 => println!("一"),
        2 | 3 | 5 | 7 | 11 | 13 => println!("质数"),
        13..=19 => println!("青少年"),  // 范围匹配
        _ => println!("其他数字"),
    }
    
    // 匹配枚举
    enum Coin {
        Penny,
        Nickel,
        Dime,
        Quarter(UsState),  // 携带数据
    }
    
    #[derive(Debug)]
    enum UsState {
        Alabama,
        Alaska,
    }
    
    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
            }
        }
    }
    
    let coin = Coin::Quarter(UsState::Alaska);
    println!("价值: {} 分", value_in_cents(coin));
}

解构模式

fn main() {
    // 解构结构体
    struct Point {
        x: i32,
        y: i32,
    }
    
    let point = Point { x: 0, y: 7 };
    match point {
        Point { x, y: 0 } => println!("在 x 轴上,x = {}", x),
        Point { x: 0, y } => println!("在 y 轴上,y = {}", y),
        Point { x, y } => println!("在任意位置 ({}, {})", x, y),
    }
    
    // 解构枚举
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }
    
    let msg = Message::ChangeColor(0, 160, 255);
    match msg {
        Message::Quit => println!("退出"),
        Message::Move { x: 0, y } => println!("向上移动 {}", y),
        Message::Move { x, y } => println!("移动到 ({}, {})", x, y),
        Message::Write(text) => println!("文本: {}", text),
        Message::ChangeColor(r, g, b) => println!("颜色: RGB({}, {}, {})", r, g, b),
    }
    
    // 解构元组
    let tuple = (1, 2, 3);
    match tuple {
        (0, y, z) => println!("第一个是 0,后面是 {} {}", y, z),
        (x, 0, z) => println!("第二个是 0,前后是 {} {}", x, z),
        (x, y, 0) => println!("第三个是 0,前面是 {} {}", x, y),
        (x, y, z) => println!("三个非零: {} {} {}", x, y, z),
    }
    
    // 解构数组/切片
    let arr = [1, 2, 3];
    match arr {
        [0, second, third] => println!("第一个是 0,后面 {} {}", second, third),
        [first, 0, third] => println!("第二个是 0,前后 {} {}", first, third),
        [first, second] => println!("只有两个元素: {} {}", first, second),  // 长度不匹配
        [first, ..] => println!("至少一个元素,第一个是 {}", first),
        [.., last] => println!("至少一个元素,最后一个是 {}", last),
        [first, .., last] => println!("至少两个元素,首 {} 尾 {}", first, last),
    }
}

绑定模式

fn main() {
    // @ 绑定:同时匹配和绑定
    enum Message {
        Hello { id: i32 },
    }
    
    let msg = Message::Hello { id: 5 };
    match msg {
        Message::Hello { id: id_variable @ 3..=7 } => {
            println!("找到 id 在 3-7 范围: {}", id_variable);
        }
        Message::Hello { id: 10..=12 } => {
            println!("找到 id 在 10-12 范围");
        }
        Message::Hello { id } => {
            println!("找到其他 id: {}", id);
        }
    }
}

匹配守卫(Match Guards)

fn main() {
    let num = Some(4);
    
    match num {
        Some(x) if x < 5 => println!("小于 5: {}", x),
        Some(x) => println!("大于或等于 5: {}", x),
        None => println!("没有值"),
    }
    
    // 多条件守卫
    let pair = (2, -2);
    match pair {
        (x, y) if x == y => println!("相等"),
        (x, y) if x + y == 0 => println!("和为零"),
        (x, _) if x % 2 == 1 => println!("第一个是奇数"),
        _ => println!("不满足任何条件"),
    }
}

5. if let 和 while let

if let 简化模式匹配

fn main() {
    let some_value = Some(3);
    
    // 使用 match
    match some_value {
        Some(3) => println!("三是三"),
        _ => (),
    }
    
    // 使用 if let(只关心一个模式时更简洁)
    if let Some(3) = some_value {
        println!("三是三");
    }
    
    // if let else
    let some_value = Some(5);
    if let Some(3) = some_value {
        println!("三是三");
    } else {
        println!("不是三");
    }
    
    // 解构结构体
    struct Point {
        x: i32,
        y: i32,
    }
    
    let point = Point { x: 0, y: 7 };
    if let Point { x: 0, y } = point {
        println!("在 y 轴上,y = {}", y);
    }
}

while let 循环匹配

fn main() {
    // 循环处理 Option
    let mut stack = Vec::new();
    stack.push(1);
    stack.push(2);
    stack.push(3);
    
    // 持续 pop 直到 None
    while let Some(top) = stack.pop() {
        println!("弹出: {}", top);
    }
    
    // 处理迭代器
    let mut iter = (1..5).into_iter();
    while let Some(n) = iter.next() {
        println!("迭代: {}", n);
    }
}

6. matches! 宏

fn main() {
    let x = 5;
    
    // matches! 返回 bool,适合简单匹配
    if matches!(x, 1..=10) {
        println!("x 在 1-10 范围");
    }
    
    // 模式守卫
    if matches!(x, n if n > 3) {
        println!("x 大于 3");
    }
    
    // 枚举匹配
    enum Color {
        Red,
        Green,
        Blue,
    }
    
    let color = Color::Red;
    if matches!(color, Color::Red | Color::Green) {
        println!("红色或绿色");
    }
}

第三层:最佳实践(Production Ready)

1. 优先使用 Option 而非 null

// ❌ 不推荐:使用 null 表示缺失
fn find_user(id: u64) -> User? {  // Rust 没有 null!
    // ...
}

// ✅ 推荐:使用 Option 显式表示可能缺失
fn find_user(id: u64) -> Option<User> {
    // 如果找到,返回 Some(user)
    // 如果没找到,返回 None
}

2. 使用 Result 而非异常

// ❌ 不推荐:抛出异常
fn parse_int(s: &str) -> i32 throws ParseError {  // Rust 没有 throws!
    // ...
}

// ✅ 推荐:使用 Result
fn parse_int(s: &str) -> Result<i32, ParseIntError> {
    s.parse::<i32>()
}

3. 使用 ? 运算符链式处理

use std::fs::File;
use std::io::{self, Read};

// ❌ 不推荐:手动处理每个错误
fn read_file_bad(path: &str) -> io::Result<String> {
    let file = match File::open(path) {
        Ok(f) => f,
        Err(e) => return Err(e),
    };
    let mut contents = String::new();
    match file.read_to_string(&mut contents) {
        Ok(_) => Ok(contents),
        Err(e) => Err(e),
    }
}

// ✅ 推荐:使用 ? 运算符
fn read_file_good(path: &str) -> io::Result<String> {
    let mut file = File::open(path)?;  // 失败时自动返回 Err
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

// ✅ 更推荐:一行搞定
fn read_file_best(path: &str) -> io::Result<String> {
    std::fs::read_to_string(path)
}

4. 使用组合子而非 match

// ❌ 不推荐:过度使用 match
fn process_option_bad(opt: Option<i32>) -> Option<i32> {
    match opt {
        Some(x) => match x {
            0 => None,
            _ => Some(x * 2),
        },
        None => None,
    }
}

// ✅ 推荐:使用组合子
fn process_option_good(opt: Option<i32>) -> Option<i32> {
    opt.filter(|&x| x != 0).map(|x| x * 2)
}

// ✅ 更推荐:使用 and_then
fn process_option_best(opt: Option<i32>) -> Option<i32> {
    opt.and_then(|x| {
        if x == 0 { None } else { Some(x * 2) }
    })
}

5. 使用 if let 处理单一情况

// ❌ 不推荐:只关心一个分支却写完整 match
fn handle_message_bad(msg: Message) {
    match msg {
        Message::Quit => println!("退出"),
        _ => (),  // 空分支
    }
}

// ✅ 推荐:使用 if let
fn handle_message_good(msg: Message) {
    if let Message::Quit = msg {
        println!("退出");
    }
}

6. 定义清晰的错误类型

use std::fmt;
use std::error::Error;

// ✅ 推荐:定义清晰的错误类型
#[derive(Debug, Clone)]
pub enum AppError {
    Io(String),
    Parse(String),
    Network(String),
    NotFound(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::Io(msg) => write!(f, "IO 错误: {}", msg),
            AppError::Parse(msg) => write!(f, "解析错误: {}", msg),
            AppError::Network(msg) => write!(f, "网络错误: {}", msg),
            AppError::NotFound(msg) => write!(f, "未找到: {}", msg),
        }
    }
}

impl Error for AppError {}

// 转换其他错误类型
impl From<std::io::Error> for AppError {
    fn from(e: std::io::Error) -> Self {
        AppError::Io(e.to_string())
    }
}

impl From<std::num::ParseIntError> for AppError {
    fn from(e: std::num::ParseIntError) -> Self {
        AppError::Parse(e.to_string())
    }
}

7. 使用 unwrap/expect 的场景

// ⚠️ unwrap/expect 在以下场景可以使用:

// 1. 测试代码
#[test]
fn test_example() {
    let result = parse("42").unwrap();  // 测试中可以 panic
    assert_eq!(result, 42);
}

// 2. 原型开发
fn prototype() {
    let config = load_config().unwrap();  // 快速原型,稍后处理错误
}

// 3. 确定不会失败的情况
fn safe_example() {
    let x: i32 = "42".parse().unwrap();  // 我们知道这是有效的
}

// 4. 使用 expect 提供上下文
fn main() {
    let file = File::open("config.toml")
        .expect("配置文件必须存在,请检查项目根目录");
}

第四层:问题诊断(Troubleshooting)

问题 1:match 穷尽性检查失败

症状:编译错误 "non-exhaustive patterns"

enum Status {
    Active,
    Inactive,
    Pending,
}

fn check_status(s: Status) {
    match s {
        Status::Active => println!("活跃"),
        Status::Inactive => println!("不活跃"),
        // 编译错误!缺少 Pending
    }
}

诊断

  • 编译器检测到枚举变体未被覆盖
  • 必须处理所有变体或使用 _ 通配符

解决方案

fn check_status(s: Status) {
    match s {
        Status::Active => println!("活跃"),
        Status::Inactive => println!("不活跃"),
        Status::Pending => println!("待处理"),
    }
}

// 或者使用通配符
fn check_status(s: Status) {
    match s {
        Status::Active => println!("活跃"),
        _ => println!("其他状态"),  // 覆盖剩余所有变体
    }
}

问题 2:unwrap 导致 panic

症状:程序运行时崩溃 "called Option::unwrap() on a None value"

fn main() {
    let result: Option<i32> = None;
    let value = result.unwrap();  // panic!
}

诊断

  • unwrap 在 None 或 Err 上会 panic
  • 应使用更安全的替代方法

解决方案

fn main() {
    let result: Option<i32> = None;
    
    // 方案 1:提供默认值
    let value = result.unwrap_or(0);
    
    // 方案 2:使用 match
    match result {
        Some(v) => println!("值: {}", v),
        None => println!("没有值"),
    }
    
    // 方案 3:使用 if let
    if let Some(v) = result {
        println!("值: {}", v);
    }
    
    // 方案 4:使用 expect(提供自定义信息)
    // let value = result.expect("必须提供值");
}

问题 3:Option/Result 链式调用错误

症状:类型不匹配或方法不存在

fn main() {
    let opt: Option<i32> = Some(5);
    
    // 错误:Option 没有 len 方法
    let len = opt.len();  // 错误!
    
    // 错误:链式调用类型推断失败
    let result = opt.map(|x| x.to_string()).map(|s| s.len());
    // 类型推断可能失败
}

诊断

  • Option 和 Result 有特定的组合方法
  • 需要理解链式调用的类型变化

解决方案

fn main() {
    let opt: Option<i32> = Some(5);
    
    // 正确:先 map 转换类型
    let result = opt
        .map(|x| x.to_string())  // Option<i32> -> Option<String>
        .map(|s| s.len());       // Option<String> -> Option<usize>
    
    println!("{:?}", result);  // Some(1)
    
    // 使用 and_then 进行更复杂的转换
    let result = opt.and_then(|x| {
        if x > 0 {
            Some(x.to_string())
        } else {
            None
        }
    });
}

问题 4:模式匹配中的所有权转移

症状:match 后原值无法使用

fn main() {
    let msg = Message::Write(String::from("Hello"));
    
    match msg {
        Message::Write(text) => println!("{}", text),
        _ => (),
    }
    
    println!("{:?}", msg);  // 错误!msg 已被部分移动
}

诊断

  • match 默认会移动值
  • 某些变体解构会获取所有权

解决方案

fn main() {
    let msg = Message::Write(String::from("Hello"));
    
    // 方案 1:使用引用
    match &msg {
        Message::Write(text) => println!("{}", text),
        _ => (),
    }
    println!("{:?}", msg);  // 正常使用
    
    // 方案 2:使用 ref 关键字
    match msg {
        Message::Write(ref text) => println!("{}", text),
        _ => (),
    }
    println!("{:?}", msg);  // 正常使用
    
    // 方案 3:Copy 类型自动复制
    let num = Some(5);
    match num {
        Some(x) => println!("{}", x),
        None => (),
    }
    println!("{:?}", num);  // 正常,因为 i32 是 Copy 类型
}

问题 5:if let 无法处理多个分支

症状:想处理两种情况,if let 只能处理一种

fn main() {
    let opt = Some(5);
    
    // 想同时处理 Some 和 None
    if let Some(x) = opt {
        println!("有值: {}", x);
    }
    // 没有 else if let!
}

诊断

  • if let 设计为简化单一模式匹配
  • 需要处理多个分支时应使用 match

解决方案

fn main() {
    let opt = Some(5);
    
    // 方案 1:使用 if let + else
    if let Some(x) = opt {
        println!("有值: {}", x);
    } else {
        println!("没有值");
    }
    
    // 方案 2:使用 match(可以处理多个分支)
    match opt {
        Some(0) => println!("零"),
        Some(x) if x > 10 => println!("大于 10: {}", x),
        Some(x) => println!("其他: {}", x),
        None => println!("没有值"),
    }
}

第五层:权威引用(References)

官方文档

资源链接
The Rust Book - Enumsdoc.rust-lang.org/book/ch06-0…
The Rust Book - Pattern Matchingdoc.rust-lang.org/book/ch18-0…
Rust Reference - Enum Typesdoc.rust-lang.org/reference/i…
std::option Moduledoc.rust-lang.org/std/option/
std::result Moduledoc.rust-lang.org/std/result/

标准库文档

// Option 常用方法
impl<T> Option<T> {
    // 构造
    fn Some(T) -> Option<T>
    fn None -> Option<T>
    
    // 提取
    fn unwrap(self) -> T
    fn unwrap_or(self, default: T) -> T
    fn unwrap_or_else<F>(self, f: F) -> T
    fn unwrap_default(self) -> T  // T: Default
    
    // 转换
    fn map<U, F>(self, f: F) -> Option<U>
    fn map_or<U, F>(self, default: U, f: F) -> U
    fn and_then<U, F>(self, f: F) -> Option<U>
    fn or_else<F>(self, f: F) -> Option<T>
    fn filter<F>(self, predicate: F) -> Option<T>
    
    // 查询
    fn is_some(&self) -> bool
    fn is_none(&self) -> bool
    fn contains(&self, x: &T) -> bool  // T: PartialEq
    
    // 操作
    fn take(&mut self) -> Option<T>
    fn replace(&mut self, value: T) -> Option<T>
}

// Result 常用方法
impl<T, E> Result<T, E> {
    // 构造
    fn Ok(T) -> Result<T, E>
    fn Err(E) -> Result<T, E>
    
    // 提取
    fn unwrap(self) -> T
    fn unwrap_err(self) -> E
    fn unwrap_or(self, default: T) -> T
    fn unwrap_or_else<F>(self, op: F) -> T
    
    // 转换
    fn map<U, F>(self, op: F) -> Result<U, E>
    fn map_err<F, O>(self, op: O) -> Result<T, F>
    fn and_then<U, F>(self, op: F) -> Result<U, E>
    fn or_else<F, O>(self, op: O) -> Result<T, F>
    
    // 查询
    fn is_ok(&self) -> bool
    fn is_err(&self) -> bool
}

错误处理最佳实践

社区资源


总结

核心概念对比

概念说明使用场景
enum代数数据类型表示有限集合、带数据变体
Option可能缺失的值替代 null、安全的可选值
Result<T, E>可能失败的操作错误处理、替代异常
match穷尽性模式匹配处理所有可能情况
if let单一模式匹配只关心一个分支
? 运算符错误传播链式处理 Result/Option

Option vs Result

特性OptionResult
语义值存在/不存在操作成功/失败
变体Some(T) / NoneOk(T) / Err(E)
错误信息有(Err(E))
典型场景查找、可选配置IO、解析、网络请求

方法速查表

目的Option 方法Result 方法
提取值unwrap, unwrap_orunwrap, unwrap_or
转换值map, and_thenmap, and_then
转换错误-map_err
提供备选or, or_elseor, or_else
过滤filter-
链式处理and_thenand_then