Chapter 05

结构体、枚举与模式匹配

Rust 的代数类型系统——用枚举精确表达所有可能的状态,用模式匹配安全处理每一种情况

结构体(Struct)

定义与实例化

结构体是 Rust 中组织相关数据的基本方式,类似于其他语言中的类(Class),但 Rust 将数据定义(struct)和行为定义(impl)明确分离:

// 定义结构体
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    // 创建结构体实例(所有字段必须初始化)
    let mut user1 = User {
        email: String::from("user@example.com"),
        username: String::from("rustacean"),
        active: true,
        sign_in_count: 1,
    };

    // 访问和修改字段(必须整个实例是 mut)
    user1.email = String::from("new@example.com");

    // 结构体更新语法:用已有实例创建新实例
    let user2 = User {
        email: String::from("another@example.com"),
        ..user1  // 其余字段来自 user1(注意:会 move user1 中的 String 字段)
    };
    println!("{}", user2.username);
}

元组结构体与单元结构体

// 元组结构体:字段没有名字,通过索引访问
struct Color(u8, u8, u8);   // RGB
struct Point(f64, f64, f64); // 3D 坐标

// 单元结构体:没有任何字段,用于实现 Trait 时
struct AlwaysEqual;

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0.0, 0.0, 0.0);
    println!("Red: {}, Green: {}, Blue: {}", black.0, black.1, black.2);
}

方法(impl 块)

#[derive(Debug)]
struct Rectangle {
    width: f64,
    height: f64,
}

// impl 块:为结构体定义方法和关联函数
impl Rectangle {
    // 关联函数(类似 static 方法):没有 &self 参数
    pub fn new(width: f64, height: f64) -> Self {
        Rectangle { width, height }
    }

    // 方法:第一个参数是 &self(不可变借用)
    fn area(&self) -> f64 {
        self.width * self.height
    }

    fn perimeter(&self) -> f64 {
        2.0 * (self.width + self.height)
    }

    // 可变方法:第一个参数是 &mut self
    fn scale(&mut self, factor: f64) {
        self.width *= factor;
        self.height *= factor;
    }

    // 方法可以接受其他实例的引用
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let mut rect = Rectangle::new(10.0, 5.0);
    println!("面积: {:.1}", rect.area());        // 50.0
    println!("周长: {:.1}", rect.perimeter());   // 30.0
    rect.scale(2.0);
    println!("{:?}", rect);  // Rectangle { width: 20.0, height: 10.0 }
}

枚举(Enum)

枚举:比其他语言强大得多

Rust 的枚举远比 C/Java 中的枚举强大——每个枚举变体可以携带不同类型和数量的数据,这使得 Rust 枚举成为一种代数数据类型(Algebraic Data Type, ADT),能精确表达业务逻辑中所有可能的状态。

// 简单枚举
enum Direction { North, South, East, West }

// 携带数据的枚举——每个变体可以有不同的数据类型
#[derive(Debug)]
enum Message {
    Quit,                         // 无数据
    Move { x: i32, y: i32 },     // 匿名结构体
    Write(String),               // 单个字符串
    ChangeColor(u8, u8, u8),    // 三个 u8
}

impl Message {
    fn call(&self) {
        // 稍后用 match 处理每种情况
        println!("{:?}", self);
    }
}

fn main() {
    let msgs = vec![
        Message::Quit,
        Message::Move { x: 10, y: 20 },
        Message::Write(String::from("hello")),
        Message::ChangeColor(255, 128, 0),
    ];
    for msg in &msgs { msg.call(); }
}

Option<T>:用枚举消灭空指针

Rust 没有 null。这是有意为之的设计决策——null 引用的发明者 Tony Hoare 自称这是"billion-dollar mistake"(价值十亿美元的错误),因为它在全球软件系统中造成了无数崩溃和漏洞。Rust 用标准库中的 Option<T> 枚举来表示"可能不存在的值":

// Option 的定义(来自标准库):
enum Option<T> {
    Some(T),  // 有值
    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, 2.0);

    // 处理 Option 值的几种方式:

    // 1. match(最完整)
    match result {
        Some(v) => println!("结果: {}", v),
        None    => println!("除以零"),
    }

    // 2. if let(只关心一种情况时)
    if let Some(v) = divide(10.0, 3.0) {
        println!("得到: {:.4}", v);
    }

    // 3. unwrap_or:提供默认值(发生 None 时)
    let safe = divide(1.0, 0.0).unwrap_or(0.0);

    // 4. ?运算符(在函数中传播)—— 见第6章
    // 5. map:变换 Some 中的值
    let doubled = divide(10.0, 2.0).map(|v| v * 2.0);
    println!("doubled: {:?}", doubled);  // Some(10.0)
}

模式匹配(Pattern Matching)

match:穷举的力量

Rust 的 match 表达式要求穷举所有可能的情况——编译器会检查你是否覆盖了枚举的所有变体。这个"穷举性检查"是 Rust 安全性的重要组成部分:当你添加新的枚举变体时,编译器会立即提醒你更新所有 match 语句。

#[derive(Debug)]
enum Coin { Penny, Nickel, Dime, Quarter }

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny   => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel  => 5,
        Coin::Dime    => 10,
        Coin::Quarter => 25,
        // 如果漏掉一个,编译器报错
    }
}

fn describe_number(n: i32) -> &'static str {
    match n {
        1       => "一",
        2 | 3   => "二或三",      // | 匹配多个值
        4..=9   => "四到九",      // 范围匹配
        n if n < 0 => "负数",    // 匹配守卫(guard)
        _       => "其他",        // _ 是通配符
    }
}

解构匹配

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!("颜色: #{:02X}{:02X}{:02X}", r, g, b);
        }
    }
}

// 解构结构体
let point = Point { x: 5, y: 10 };
let Point { x, y } = point;
println!("x={}, y={}", x, y);

// 解构元组
let (a, b, c) = (1, 2, 3);

// 忽略部分字段
let Point { x, .. } = point;  // 只绑定 x,忽略其他字段

if let 与 while let

fn main() {
    let config_max = Some(3u8);

    // if let:只关心一种 pattern 时,比 match 简洁
    if let Some(max) = config_max {
        println!("最大值: {}", max);
    }

    // if let 可以带 else
    if let Some(max) = config_max {
        println!("有值: {}", max);
    } else {
        println!("没有值");
    }

    // while let:条件为 pattern 匹配时持续循环
    let mut stack = Vec::new();
    stack.push(1);
    stack.push(2);
    stack.push(3);

    while let Some(top) = stack.pop() {
        print!("{} ", top);  // 3 2 1
    }
}
让非法状态不可表示

Rust 枚举的强大之处在于可以利用类型系统"让非法状态不可表示"(Making Illegal States Unrepresentable)。例如,用 Option<Email> 而不是 String 来表示可选的邮箱地址,这样类型本身就告诉了你这个字段可能不存在,而编译器强制你处理这种情况。这是领域驱动设计(DDD)思想在类型系统中的体现。