跳转至

结构体与枚举

结构体与枚举

📚 章节概述

本章将介绍 Rust 中两种重要的自定义数据类型:结构体(struct)和枚举(enum)。结构体用于组织相关数据,枚举用于定义一组可能的值。我们还将学习模式匹配,这是处理枚举的强大工具。

🎯 学习目标

  • 掌握结构体的定义和使用
  • 学会定义方法和关联函数
  • 理解枚举的概念和应用
  • 掌握模式匹配的用法
  • 学会使用 Option 和 Result 枚举

📖 结构体

1.1 定义结构体

结构体是组织相关数据的方式:

Rust
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };
}

1.2 访问结构体字段

Rust
fn main() {
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };

    println!("Username: {}", user1.username);
    println!("Email: {}", user1.email);
}

1.3 结构体更新语法

Rust
fn main() {
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };

    // 使用结构体更新语法,..user1 表示其余字段从 user1 复制
    // 注意:String 类型字段会发生所有权转移(move)
    let user2 = User {
        email: String::from("another@example.com"),
        ..user1  // 从 user1 中取剩余字段的值
    };

    println!("User2 email: {}", user2.email);
    // println!("{}", user1.username); // 编译错误!user1 的 username 已被移动
}

1.4 元组结构体

Rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);

    println!("Black: ({}, {}, {})", black.0, black.1, black.2);
}

1.5 类单元结构体

Rust
struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

🔧 方法

2.1 定义方法

方法与函数类似,但在结构体的上下文中定义:

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

impl Rectangle {  // impl 为类型实现方法
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

2.2 关联函数

不使用 self 的函数称为关联函数:

Rust
impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let sq = Rectangle::square(3);
    println!("Square: {}x{}", sq.width, sq.height);
}

2.3 多个 impl 块

Rust
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

impl Rectangle {
    // 判断当前矩形是否能完全包含另一个矩形
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
}

🔢 枚举

3.1 定义枚举

枚举允许你定义一个类型,该类型的值可以是几个可能的变体之一:

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

fn main() {
    let home = IpAddr::V4(String::from("127.0.0.1"));
    let loopback = IpAddr::V6(String::from("::1"));
}

3.2 枚举变体

Rust
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let m = Message::Write(String::from("hello"));
}

3.3 枚举上的方法

Rust
impl Message {
    fn call(&self) {
        // 使用 match 对枚举的每个变体进行模式匹配
        // 每个分支可以解构出变体中携带的数据
        match self {  // match 模式匹配,穷举所有可能的值
            Message::Quit => println!("Quit"),
            Message::Move { x, y } => println!("Move to ({}, {})", x, y),      // 解构命名字段
            Message::Write(s) => println!("Write: {}", s),                       // 解构元组字段
            Message::ChangeColor(r, g, b) => println!("Change color to ({}, {}, {})", r, g, b),
        }
    }
}

fn main() {
    let m = Message::Write(String::from("hello"));
    m.call();
}

🎯 Option 枚举

4.1 Option 定义

Option 是 Rust 中处理可能为空的值的枚举:

Rust
enum Option<T> {  // Option 表示值可能存在(Some)或不存在(None)
    Some(T),
    None,
}

4.2 使用 Option

Rust
fn main() {
    let some_number = Some(5);
    let some_string = Some("a string");
    let absent_number: Option<i32> = None;

    println!("some_number: {:?}", some_number);
    println!("some_string: {:?}", some_string);
    println!("absent_number: {:?}", absent_number);
}

4.3 处理 Option

Rust
fn main() {
    let some_number = Some(5);

    match some_number {
        Some(value) => println!("Got value: {}", value),
        None => println!("No value"),
    }

    // 使用 if let
    if let Some(value) = some_number {
        println!("Got value: {}", value);
    }
}

🎭 模式匹配

5.1 match 表达式

Rust
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

fn main() {
    let coin = Coin::Penny;
    println!("Value: {} cents", value_in_cents(coin));
}

5.2 绑定值

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

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

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        // 匹配 Quarter 变体时,将内部的 UsState 值绑定到 state 变量
        Coin::Quarter(state) => {
            println!("State quarter from {:?}!", state);
            25
        }
    }
}

fn main() {
    let coin = Coin::Quarter(UsState::Alaska);
    println!("Value: {} cents", value_in_cents(coin));
}

5.3 匹配 Option

Rust
// 对 Option 进行模式匹配,实现安全的值转换
// None 保持为 None,Some(i) 则取出值加1后重新包装
fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

fn main() {
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);

    println!("five: {:?}", five);
    println!("six: {:?}", six);
    println!("none: {:?}", none);
}

5.4 匹配所有可能性

Rust
fn main() {
    let dice_roll = 9;

    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        other => move_player(other), // other 是通配模式,捕获所有未匹配的值并绑定到变量
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn move_player(num_spaces: u8) {}
}

5.5 if let 简化匹配

Rust
fn main() {
    let some_value = Some(0u8);

    match some_value {
        Some(3) => println!("three"),
        _ => println!("not three"),
    }

    // 使用 if let
    if let Some(3) = some_value {
        println!("three");
    } else {
        println!("not three");
    }
}

📝 练习题

练习 1:结构体定义

Rust
// TODO: 定义一个 Book 结构体,包含:
// - title: String
// - author: String
// - pages: u32
// - price: f64

// TODO: 为 Book 实现一个方法 total_cost(quantity: u32) -> f64

fn main() {
    // TODO: 创建一个 Book 实例并测试
}

练习 2:枚举定义

Rust
// TODO: 定义一个 TrafficLight 枚举,包含:
// - Red
// - Yellow
// - Green

// TODO: 为 TrafficLight 实现一个方法 duration() -> u32
// Red: 30秒, Yellow: 5秒, Green: 45秒

fn main() {
    // TODO: 测试 TrafficLight
}

练习 3:模式匹配

Rust
enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
    Square(f64),
}

// TODO: 实现 area 函数,计算不同形状的面积
// 提示:Circle 面积 = π * r²,Rectangle = w * h,Square = s²
fn area(shape: Shape) -> f64 {
    // 使用 match 表达式对 Shape 的每个变体进行匹配
    todo!()
}

fn main() {
    let circle = Shape::Circle(5.0);
    let rectangle = Shape::Rectangle(4.0, 6.0);
    let square = Shape::Square(3.0);

    println!("Circle area: {}", area(circle));
    println!("Rectangle area: {}", area(rectangle));
    println!("Square area: {}", area(square));
}

练习 4:Option 处理

Rust
// TODO: 编写一个函数,将字符串转换为整数
// 如果转换成功,返回 Some(i32)
// 如果转换失败,返回 None
fn parse_int(s: &str) -> Option<i32> {
    // TODO: 实现
    todo!()
}

fn main() {
    let valid = "123";
    let invalid = "abc";

    println!("Parse '{}': {:?}", valid, parse_int(valid));
    println!("Parse '{}': {:?}", invalid, parse_int(invalid));
}

💡 最佳实践

1. 使用结构体组织相关数据

Rust
// 好
struct User {
    name: String,
    email: String,
    age: u32,
}

// 避免
fn create_user(name: String, email: String, age: u32) {
    // 使用多个参数
}

2. 使用枚举表示有限的选项

Rust
// 好
enum Status {
    Active,
    Inactive,
    Pending,
}

// 避免
struct Status {
    is_active: bool,
    is_pending: bool,
}

3. 使用 Option 而不是空值

Rust
// 好
fn find_user(id: u32) -> Option<User> {
    // 返回 Option<User>
}

// 避免
fn find_user(id: u32) -> User {
    // 返回空 User 或使用 panic!
}

4. 使用模式匹配处理枚举

Rust
// 好
fn process_status(status: Status) {
    match status {
        Status::Active => println!("User is active"),
        Status::Inactive => println!("User is inactive"),
        Status::Pending => println!("User is pending"),
    }
}

// 避免
fn process_status(status: Status) {
    if status == Status::Active {
        println!("User is active");
    }
    // 需要多个 if 语句
}

⚠️ 常见错误

1. 忘记实现 trait

Rust
// 错误
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("{:?}", rect); // 编译错误!Rectangle 没有实现 Debug
}

// 正确
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("{:?}", rect);
}

2. match 不完整

Rust
// 错误
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        // 编译错误!没有处理所有情况
    }
}

// 正确
fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

3. 忽略 None

Rust
// 错误
fn divide(a: f64, b: f64) -> f64 {
    a / b // 如果 b 为 0,会返回 inf
}

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

📚 扩展阅读

🎯 本章小结

本章介绍了 Rust 中的结构体和枚举:

  • ✅ 掌握结构体的定义和使用
  • ✅ 学会定义方法和关联函数
  • ✅ 理解枚举的概念和应用
  • ✅ 掌握模式匹配的用法
  • ✅ 学会使用 Option 枚举处理可能为空的值

下一章: 我们将学习 Rust 的错误处理机制,包括 Result 类型、Option 类型和错误传播。