核心特性

第9课:枚举与Option

🎯 本课目标

掌握Rust枚举定义、Option类型、模式匹配枚举,以及代数数据类型概念。

1. 枚举定义

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

enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Triangle { a: f64, b: f64, c: f64 },
}

fn area(s: &Shape) -> f64 {
    match s {
        Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
        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 main() {
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));

    let shapes: Vec<Shape> = vec![
        Shape::Circle { radius: 5.0 },
        Shape::Rectangle { width: 3.0, height: 4.0 },
        Shape::Triangle { a: 3.0, b: 4.0, c: 5.0 },
    ];
    for s in &shapes {
        println!("面积: {:.2}", area(s));
    }
}

✅ 编译验证通过

2. Option — 空值的替代

🔑 Tony Hoare的"十亿美元错误":null引用。Rust没有null,用Option<T>替代:
enum Option<T> {
    Some(T),
    None,
}

编译器强制你处理None的情况,不会遗漏空值检查。

fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 { None } else { Some(a / b) }
}

fn main() {
    let result = divide(10.0, 3.0);
    match result {
        Some(v) => println!("结果: {:.2}", v),
        None => println!("除零错误"),
    }

    // if let简洁写法
    if let Some(v) = divide(10.0, 2.0) {
        println!("if let: {}", v);
    }

    // unwrap — None时panic
    // let v = divide(10.0, 0.0).unwrap();  // 💥

    // unwrap_or — 提供默认值
    let v = divide(10.0, 0.0).unwrap_or(0.0);
    println!("默认值: {}", v);
}

✅ 编译验证通过

3. Option的常用方法

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

    // map — 转换Some中的值
    let doubled = some.map(|x| x * 2);     // Some(84)
    let none_d = none.map(|x| x * 2);     // None

    // and_then — 链式操作(flatMap)
    let result = Some(2)
        .and_then(|x| divide(10.0, x as f64))
        .map(|v| v * 3.0);

    // filter — 条件过滤
    let positive = Some(5).filter(|&x| x > 0);  // Some(5)
    let negative = Some(-5).filter(|&x| x > 0); // None

    // unwrap_or_else
    let v = None.<i32>.unwrap_or_else(|| 0);

    // ok_or — 转为Result
    let r = None.<i32>.ok_or("not found");
}

fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 { None } else { Some(a / b) }
}

✅ 编译验证通过

4. 代数数据类型

Rust枚举是代数数据类型(ADT),有两种:

类型状态数量:enum是变体之和,struct是字段之积。

枚举的内存布局: enum Option<i32> { Some(i32), None, } ┌──────────────┐ │ tag: 0 (Some)│ 1 byte + 7 padding │ value: 42 │ 4 bytes + 4 padding └──────────────┘ 总计: 16 bytes (64位) ┌──────────────┐ │ tag: 1 (None)│ 1 byte + 7 padding │ (无数据) │ └──────────────┘ 优化:Option<&i32>只需8字节! 因为引用不能为null,None用null指针表示 ——这是Rust的"niche optimization"

5. 枚举与链表

enum List {
    Cons(i32, Box<List>),
    Nil,
}

fn sum(list: &List) -> i32 {
    match list {
        List::Cons(head, tail) => head + sum(tail),
        List::Nil => 0,
    }
}

fn main() {
    let list = List::Cons(1,
        Box::new(List::Cons(2,
            Box::new(List::Cons(3,
                Box::new(List::Nil))))));
    println!("sum = {}", sum(&list));
}

✅ 编译验证通过

📝 练习题

练习1:Option链式操作

用Option的链式方法实现:解析字符串为整数→除以2→转换为字符串。

练习2:二叉树

用枚举实现二叉搜索树,支持insert和search。

练习3:状态机

用枚举实现一个TCP连接状态机(Closed→Listen→SynSent→Established)。

🏆

成就解锁:类型设计师

掌握枚举和Option——Rust类型系统的核心工具


📚 扩展阅读

以下资源帮助你深入学习本课主题:

🔑 关键术语回顾

本课涉及的核心概念,确保你理解每一个:

术语说明
所有权Rust内存管理的核心机制,每个值有唯一所有者
借用通过引用访问数据,不获取所有权
生命周期引用有效的范围,编译期分析工具
traitRust的接口/抽象机制,类似其他语言的接口
泛型参数化类型,零成本抽象
模式匹配强大的数据解构和分支机制
零成本抽象高层抽象不引入运行时开销
fearless concurrency编译器保证线程安全

💬 学习建议

  1. 动手编码 — 每个代码示例都在本地运行一遍
  2. 修改实验 — 故意改错代码,看编译器报什么错
  3. 完成练习 — 每课的练习题是巩固知识的关键
  4. 阅读源码 — 看标准库和优秀开源项目的实现
  5. 写项目 — 真正掌握需要构建真实项目

🤔 常见问题

Q: Rust学习曲线真的很陡吗?

A: 前期确实需要适应所有权和借用检查器,但一旦理解了,这些概念会让你的代码更可靠。大多数人2-4周就能上手。

Q: Rust适合什么项目?

A: 系统编程、Web服务、CLI工具、嵌入式、WASM、网络服务、数据库等。基本上需要性能和安全的地方都适合。

Q: 遇到编译错误怎么办?

A: Rust编译器的错误信息非常友好!仔细阅读,通常会指出问题所在和修复建议。也可以用cargo clippy获取更多提示。