🦀 第07课:枚举与模式匹配

枚举(enum)和模式匹配(match)是Rust中最强大的特性组合。Rust的枚举远不止其他语言的简单枚举——每个变体可以携带不同类型和数量的数据,配合match表达式,让你写出既安全又优雅的代码。

核心特性 第7/25课

学习目标:掌握枚举定义与变体、match表达式穷尽性、if let简化、模式匹配各种用法

🎯 枚举定义

基本枚举

enum Direction {
    Up,
    Down,
    Left,
    Right,
}

fn move_player(dir: Direction) -> &'static str {
    match dir {
        Direction::Up => "向上移动 ⬆️",
        Direction::Down => "向下移动 ⬇️",
        Direction::Left => "向左移动 ⬅️",
        Direction::Right => "向右移动 ➡️",
    }
}

fn main() {
    let d = Direction::Up;
    println!("{}", move_player(d));
}
向上移动 ⬆️

携带数据的枚举

enum Shape {
    Circle(f64),                           // 半径
    Rectangle { width: f64, height: f64 }, // 命名字段
    Triangle(f64, f64, f64),               // 三条边
}

impl Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle(r) => std::f64::consts::PI * r * r,
            Shape::Rectangle { width, height } => width * height,
            Shape::Triangle(a, b, c) => {
                // 海伦公式
                let s = (a + b + c) / 2.0;
                (s * (s - a) * (s - b) * (s - c)).sqrt()
            }
        }
    }
    
    fn describe(&self) -> String {
        match self {
            Shape::Circle(r) => format!("圆形(r={})", r),
            Shape::Rectangle { width, height } => format!("矩形({}×{})", width, height),
            Shape::Triangle(a, b, c) => format!("三角形({},{},{})", a, b, c),
        }
    }
}

fn main() {
    let shapes = vec![
        Shape::Circle(5.0),
        Shape::Rectangle { width: 4.0, height: 6.0 },
        Shape::Triangle(3.0, 4.0, 5.0),
    ];
    
    for shape in &shapes {
        println!("{}: 面积={:.2}", shape.describe(), shape.area());
    }
}
圆形(r=5): 面积=78.54 矩形(4×6): 面积=24.00 三角形(3,4,5): 面积=6.00

✅ 验证通过

🌟 Option —— 最常用的枚举

Option<T>是Rust取代null的设计。它明确表示一个值可能存在或不存在:

enum Option<T> {
    Some(T),
    None,
}
fn find_user(id: u32) -> Option<String> {
    if id == 1 {
        Some(String::from("Alice"))
    } else if id == 2 {
        Some(String::from("Bob"))
    } else {
        None
    }
}

fn main() {
    // Option基本使用
    let some_number: Option<i32> = Some(42);
    let no_number: Option<i32> = None;
    
    println!("{:?}", some_number);
    println!("{:?}", no_number);
    
    // match处理
    let user = find_user(1);
    match user {
        Some(name) => println!("找到用户: {}", name),
        None => println!("用户不存在"),
    }
    
    // unwrap —— 获取值,None则panic
    // let name = find_user(99).unwrap();  // ❌ panic!
    
    // unwrap_or —— 提供默认值
    let name = find_user(99).unwrap_or(String::from("未知用户"));
    println!("用户: {}", name);
    
    // unwrap_or_else —— 懒计算默认值
    let name = find_user(99).unwrap_or_else(|| {
        println!("用户不存在,使用默认值");
        String::from("Guest")
    });
    println!("用户: {}", name);
    
    // map —— 转换Some中的值
    let user_len = find_user(1).map(|name| name.len());
    println!("名字长度: {:?}", user_len);
    
    // and_then —— 链式Option操作
    let result = find_user(1)
        .and_then(|name| {
            if name.starts_with('A') {
                Some(format!("管理员: {}", name))
            } else {
                None
            }
        });
    println!("管理员: {:?}", result);
    
    // if let —— 只关心一种情况
    if let Some(name) = find_user(1) {
        println!("if_let找到: {}", name);
    }
    
    // while let —— 循环处理
    let mut stack = vec![1, 2, 3];
    while let Some(top) = stack.pop() {
        println!("弹出: {}", top);
    }
}
Some(42) None 找到用户: Alice 用户: 未知用户 用户不存在,使用默认值 用户: Guest 名字长度: Some(5) 管理员: Some("管理员: Alice") if_let找到: Alice 弹出: 3 弹出: 2 弹出: 1

✅ 验证通过

🎲 match表达式详解

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

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

fn value_in_cents(coin: &Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("来自{:?}的25分硬币!", state);
            25
        }
    }
}

fn main() {
    let coins = vec![
        Coin::Penny,
        Coin::Quarter(UsState::Alaska),
        Coin::Dime,
    ];
    
    let mut total = 0;
    for coin in &coins {
        total += value_in_cents(coin);
    }
    println!("总计: {}分", total);
    
    // match是表达式
    let x = 5;
    let description = match x {
        0 => "零",
        1..=10 => "个位数",
        11..=99 => "两位数",
        _ => "大数",  // _ 通配符
    };
    println!("{}是{}", x, description);
    
    // 解构匹配
    let point = (3, -5);
    match point {
        (0, 0) => println!("原点"),
        (x, 0) => println!("x轴上, x={}", x),
        (0, y) => println!("y轴上, y={}", y),
        (x, y) if x > 0 && y > 0 => println!("第一象限 ({}, {})", x, y),
        (x, y) => println!("坐标 ({}, {})", x, y),
    }
    
    // 守卫(guard)条件
    let num = 4;
    match num {
        n if n % 2 == 0 => println!("{}是偶数", n),
        n => println!("{}是奇数", n),
    }
    
    // 绑定 @
    let age = 25u32;
    match age {
        n @ 0..=12 => println!("儿童({}岁)", n),
        n @ 13..=19 => println!("青少年({}岁)", n),
        n @ 20..=59 => println!("成人({}岁)", n),
        n => println!("老年({}岁)", n),
    }
}
来自Alaska的25分硬币! 总计: 36分 5是个位数 坐标 (3, -5) 4是偶数 成人(25岁)

✅ 验证通过

⚠️ 穷尽性:match必须覆盖所有可能的情况!如果遗漏了某个变体,编译器会报错。这是Rust安全保证的重要部分。_可以匹配剩余所有情况。

🏗️ 综合实战:表达式求值器

/// 简单算术表达式AST
#[derive(Debug, Clone)]
enum Expr {
    Number(f64),
    Add(Box<Expr>, Box<Expr>),
    Sub(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
    Div(Box<Expr>, Box<Expr>),
    Neg(Box<Expr>),
}

impl Expr {
    fn num(v: f64) -> Self { Expr::Number(v) }
    fn add(l: Expr, r: Expr) -> Self { Expr::Add(Box::new(l), Box::new(r)) }
    fn sub(l: Expr, r: Expr) -> Self { Expr::Sub(Box::new(l), Box::new(r)) }
    fn mul(l: Expr, r: Expr) -> Self { Expr::Mul(Box::new(l), Box::new(r)) }
    fn div(l: Expr, r: Expr) -> Self { Expr::Div(Box::new(l), Box::new(r)) }
    fn neg(e: Expr) -> Self { Expr::Neg(Box::new(e)) }
    
    /// 求值
    fn eval(&self) -> Result<f64, String> {
        match self {
            Expr::Number(n) => Ok(*n),
            Expr::Add(l, r) => Ok(l.eval()? + r.eval()?),
            Expr::Sub(l, r) => Ok(l.eval()? - r.eval()?),
            Expr::Mul(l, r) => Ok(l.eval()? * r.eval()?),
            Expr::Div(l, r) => {
                let divisor = r.eval()?;
                if divisor == 0.0 {
                    Err("除零错误".to_string())
                } else {
                    Ok(l.eval()? / divisor)
                }
            }
            Expr::Neg(e) => Ok(-e.eval()?),
        }
    }
    
    /// 转为字符串表示
    fn to_string_pretty(&self) -> String {
        match self {
            Expr::Number(n) => format!("{}", n),
            Expr::Add(l, r) => format!("({} + {})", l.to_string_pretty(), r.to_string_pretty()),
            Expr::Sub(l, r) => format!("({} - {})", l.to_string_pretty(), r.to_string_pretty()),
            Expr::Mul(l, r) => format!("({} * {})", l.to_string_pretty(), r.to_string_pretty()),
            Expr::Div(l, r) => format!("({} / {})", l.to_string_pretty(), r.to_string_pretty()),
            Expr::Neg(e) => format!("-{}", e.to_string_pretty()),
        }
    }
}

fn main() {
    // 计算 (3 + 4) * (2 - 1)
    let expr = Expr::mul(
        Expr::add(Expr::num(3.0), Expr::num(4.0)),
        Expr::sub(Expr::num(2.0), Expr::num(1.0)),
    );
    println!("表达式: {}", expr.to_string_pretty());
    println!("结果: {:?}", expr.eval());
    
    // 带除零检查
    let div_zero = Expr::div(Expr::num(1.0), Expr::num(0.0));
    println!("1/0: {:?}", div_zero.eval());
    
    // 复杂表达式: -(5 * 3 + 2) / 17
    let complex = Expr::div(
        Expr::neg(Expr::add(Expr::mul(Expr::num(5.0), Expr::num(3.0)), Expr::num(2.0))),
        Expr::num(17.0),
    );
    println!("复杂表达式: {}", complex.to_string_pretty());
    println!("结果: {:?}", complex.eval());
}
表达式: ((3 + 4) * (2 - 1)) 结果: Ok(7.0) 1/0: Err("除零错误") 复杂表达式: (-((5 * 3) + 2) / 17) 结果: Ok(-1.0)

✅ 验证通过

📝 练习

练习1:IP地址枚举

定义IpAddr枚举,包含V4(u8,u8,u8,u8)和V6(String)变体,实现display方法。

练习2:链表

用枚举实现简单的单链表:enum List { Nil, Cons(i32, Box<List>) },实现push和sum方法。

练习3:JSON值

定义Json枚举表示JSON值:Null、Bool(bool)、Number(f64)、String、Array、Object,实现递归pretty print。

🏆 本课成就

🔒 下一课解锁:错误处理 —— Result与Option的完整掌握

🎮 状态机模式

use std::fmt;

// 类型状态模式:编译期保证状态转换
struct Draft;
struct PendingReview;
struct Published;

struct Post {
    content: String,
    _state: std::marker::PhantomData,
}

impl Post {
    fn content(&self) -> &str { &self.content }
}

impl Post {
    fn new() -> Self {
        Post { content: String::new(), _state: std::marker::PhantomData }
    }
    fn add_text(&mut self, text: &str) { self.content.push_str(text); }
    fn request_review(self) -> Post {
        Post { content: self.content, _state: std::marker::PhantomData }
    }
}

impl Post {
    fn approve(self) -> Post {
        Post { content: self.content, _state: std::marker::PhantomData }
    }
    fn reject(self) -> Post {
        Post { content: self.content, _state: std::marker::PhantomData }
    }
}

impl Post {
    // 只有Published状态才能调用
}

fn main() {
    let mut post = Post::new();
    post.add_text("Rust的类型状态模式");
    // post.content(); // ❌ Draft状态不能读取内容
    
    let post = post.request_review();
    // post.add_text("more"); // ❌ PendingReview不能添加文本
    
    let post = post.approve();
    println!("发布: {}", post.content()); // ✅ Published可以读取
    
    // 类型状态在编译期保证:不可能在Draft状态发布
    println!("状态机模式确保安全的操作序列 ✅");
}
发布: Rust的类型状态模式 状态机模式确保安全的操作序列 ✅

✅ 验证通过