11·复合类型入门

枚举(Enum)

枚举(Enum)

学习目标

  1. 定义和使用枚举
  2. 理解枚举变体可以携带数据
  3. 掌握 Option<T>
  4. 掌握枚举上的方法

核心概念

基本枚举

enum Direction {
    North,
    South,
    East,
    West,
}

fn main() {
    let dir = Direction::North;
    // 用于 match、if let 等
}

携带数据的变体

enum Shape {
    Circle(f64),                    // 半径
    Rectangle(f64, f64),           // 宽, 高
    Triangle { base: f64, height: f64 },  // 命名字段
    Point,                          // 无数据
}

fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle(r) => std::f64::consts::PI * r * r,
        Shape::Rectangle(w, h) => w * h,
        Shape::Triangle { base, height } => 0.5 * base * height,
        Shape::Point => 0.0,
    }
}

fn main() {
    let shapes = vec![
        Shape::Circle(5.0),
        Shape::Rectangle(4.0, 6.0),
        Shape::Triangle { base: 3.0, height: 8.0 },
    ];

    for s in &shapes {
        println!("面积: {:.2}", area(s));
    }
}

枚举变体的内存布局

enum Message {
    Quit,                       // 0 字节数据
    Move { x: i32, y: i32 },   // 8 字节数据
    Write(String),              // 24 字节数据(String 大小)
    Color(i32, i32, i32),       // 12 字节数据
}
// 枚举大小 = 最大变体的大小 + 判别标签

Option<T>(标准库枚举)

Rust 没有 null,用 Option 表示"可能没有值":

// 标准库定义
enum Option<T> {
    Some(T),    // 有值
    None,       // 无值
}

fn main() {
    let some_number: Option<i32> = Some(42);
    let no_number: Option<i32> = None;

    // 必须处理 None 的情况
    match some_number {
        Some(n) => println!("数字是: {}", n),
        None => println!("没有数字"),
    }

    // if let 简写
    if let Some(n) = some_number {
        println!("数字是: {}", n);
    }
}

Option 的常用方法

fn main() {
    let x: Option<i32> = Some(5);
    let y: Option<i32> = None;

    // unwrap:有值返回,None panic
    println!("{}", x.unwrap());  // 5
    // y.unwrap();               // ❌ panic

    // unwrap_or:None 时用默认值
    println!("{}", y.unwrap_or(0));  // 0

    // map:转换 Some 内的值
    let doubled = x.map(|n| n * 2);  // Some(10)

    // and_then:链式操作
    let result = x.and_then(|n| {
        if n > 0 { Some(n * 10) } else { None }
    });

    // is_some / is_none
    println!("有值? {}", x.is_some());  // true
    println!("空? {}", y.is_none());    // true
}

枚举上的方法

enum TrafficLight {
    Red,
    Yellow,
    Green,
}

impl TrafficLight {
    fn duration(&self) -> u32 {
        match self {
            TrafficLight::Red => 60,
            TrafficLight::Yellow => 5,
            TrafficLight::Green => 45,
        }
    }

    fn next(&self) -> TrafficLight {
        match self {
            TrafficLight::Red => TrafficLight::Green,
            TrafficLight::Green => TrafficLight::Yellow,
            TrafficLight::Yellow => TrafficLight::Red,
        }
    }
}

fn main() {
    let light = TrafficLight::Red;
    println!("红灯 {} 秒", light.duration());
    let next = light.next();
    println!("下一个是绿灯");
}

实践练习

练习 1:IP 地址

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

impl IpAddr {
    fn to_string(&self) -> String {
        match self {
            IpAddr::V4(a, b, c, d) => format!("{}.{}.{}.{}", a, b, c, d),
            IpAddr::V6(addr) => addr.clone(),
        }
    }
}

fn main() {
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));
    println!("{}", home.to_string());
    println!("{}", loopback.to_string());
}

练习 2:命令枚举

enum Command {
    Print(String),
    Quit,
    Move { x: i32, y: i32 },
    Color(u8, u8, u8),
}

fn execute(cmd: &Command) {
    match cmd {
        Command::Print(msg) => println!("{}", msg),
        Command::Quit => println!("退出"),
        Command::Move { x, y } => println!("移动到 ({}, {})", x, y),
        Command::Color(r, g, b) => println!("颜色: #{:02x}{:02x}{:02x}", r, g, b),
    }
}

fn main() {
    let commands = vec![
        Command::Print(String::from("Hello")),
        Command::Move { x: 10, y: 20 },
        Command::Color(255, 128, 0),
        Command::Quit,
    ];
    for cmd in &commands {
        execute(cmd);
    }
}

常见错误

1. match 未覆盖所有变体

let x: Option<i32> = Some(5);
match x {
    Some(n) => println!("{}", n),
    // ❌ 缺少 None 分支
}

2. 滥用 unwrap

let config: Option<String> = None;
// let val = config.unwrap();  // ❌ panic
let val = config.unwrap_or(String::from("default"));  // ✅

小结

概念说明
enum定义枚举
变体枚举的每个可能值
携带数据变体可以包含不同类型的数据
Option<T>Some(T) 或 None,替代 null
match穷尽匹配枚举变体

练习编辑器

rust
Loading...