03 Rust复杂数据定义

3.1 Rust 结构体(Struct)核心概念与用法笔记

在 Rust 中,结构体(struct)用于创建自定义的数据类型,将多项相关联的值打包在一起。配合 impl 块(实现方法)和组合(Composition),你可以构建出结构清晰、职责分明的面向对象风格或函数式风格的代码架构。


一、 定义结构体(Struct Definitions)

Rust 支持三种形式的结构体,适应不同的数据组织场景:

1. 具名字段结构体(C-like Structs)

最常用的形式,每个字段都有明确的名字和类型。

##[derive(Debug)]
struct ServerConfig {
    host: String,
    port: u16,
    timeout_seconds: u64,
}

2. 元组结构体(Tuple Structs)

字段没有名字,只有类型顺序。适合给基本类型起一个“强类型别名”,避免混淆。

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

3. 单元结构体(Unit-like Structs)

没有任何字段,类似于 ()。通常用于实现某些特定特征(Trait)而不需要存储状态。

struct EmptyMarker;

二、 实现结构体中的方法(Methods & Associated Functions)

通过 impl 块,可以为结构体绑定函数。Rust 将其分为两类:

  1. 关联函数(Associated Functions / Constructors):类似其他语言的“静态方法”,通常用于构造实例(惯例命名为 new)。注意:它没有 self 参数。

  2. 方法(Methods):需要操作具体实例的函数。它们的第一个参数必须是 self 的某种形态(self、&self 或 &mut self)。

impl ServerConfig {
    // 1. 关联函数:用于构造实例
    fn new(host: String, port: u16) -> Self {
        Self {
            host,
            port,
            timeout_seconds: 30, // 默认超时时间
        }
    }

    // 2. 方法(借用不可变实例):只读取数据
    fn address(&self) -> String {
        format!("{}:{}", self.host, self.port)
    }

    // 3. 方法(借用可变实例):修改内部状态
    fn set_timeout(&mut self, timeout: u64) {
        self.timeout_seconds = timeout;
    }
}

使用示例:

fn main() {
    // 调用关联函数创建实例(必须是 mut 才能调用可变方法)
    let mut config = ServerConfig::new("127.0.0.1".to_string(), 8080);
    
    println!("Server address: {}", config.address()); // 读取
    
    config.set_timeout(60); // 修改
}

三、 通过组合产生新的结构体(Composition)

Rust 强烈推荐“组合优于继承”(Composition over Inheritance)。通过将已有的结构体作为新结构体的字段,可以轻松构建出更复杂、层级化的数据结构。

1. 包含具名字段的组合

当一个大系统由多个子模块配置组成时,直接组合:

##[derive(Debug)]
struct DatabaseConfig {
    url: String,
    max_connections: u32,
}

##[derive(Debug)]
struct AppConfig {
    name: String,
    server: ServerConfig,      // 组合了前面定义的 ServerConfig
    database: DatabaseConfig,  // 组合了 DatabaseConfig
}

实例化与访问:

let app_cfg = AppConfig {
    name: "MyService".to_string(),
    server: ServerConfig::new("0.0.0.0".to_string(), 3000),
    database: DatabaseConfig {
        url: "postgres://localhost:5432/db".to_string(),
        max_connections: 10,
    },
};

// 通过点号链式访问内层结构体的字段
println!("Database max conn: {}", app_cfg.database.max_connections);

2. 字段初始化简写与结构体更新语法(Struct Update Syntax)

当局部变量名与字段名相同时,可以直接简写;同时可以用 .. 语法基于已有实例快速派生新实例:

let host = "192.168.1.100".to_string();
let port = 9000;

// 简写初始化
let base_config = ServerConfig {
    host,
    port,
    timeout_seconds: 10,
};

// 结构体更新语法:复用 base_config 的 host 和 timeout,只修改 port
let custom_config = ServerConfig {
    port: 8081,
    ..base_config 
};

四、 核心优势总结

  • 零开销抽象:Rust 的组合在内存中是平铺布局的(没有隐藏的虚表或指针开销),性能等价于 C 语言的手动内存布局。

  • 清晰的借用控制:通过 &self、&mut self 和 self,编译器在编译期就能严格杜绝数据竞争和非法的状态修改。

3.2 Rust 枚举(Enum)核心概念与用法笔记

在 Rust 中,枚举(enum)远比传统语言(如 C/C++、Java)中的枚举强大。它们是代数数据类型(Algebraic Data Types),不仅能表示一组离散的值,还能携带丰富的数据,是构建强类型、安全架构的基石。


一、 枚举的三种形态(变体 Variants)

Rust 枚举的每个变体(Variant)都可以独立携带不同类型和结构的数据。主要分为三种形态:

1. 单元变体(Unit Variants)

不带任何数据,类似于传统的 C 风格枚举。适用于表示纯粹的状态或标签。

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

2. 元组变体(Tuple Variants)

包含按位置排序的匿名数据,类似于元组。

enum Message {
    Quit,                       // 类似单元变体
    Move { x: i32, y: i32 },     // (注意:此处示例混淆了结构体,下面修正)
    Write(String),              // 包含一个 String
    ChangeColor(i32, i32, i32), // 包含三个 i32
}

(修正上面的注释):

  • Write(String) 和 ChangeColor(i32, i32, i32) 属于元组变体。

3. 结构体变体(Struct Variants)

包含具名字段,类似于标准的 struct。

enum WorkerCount {
    Explicit(usize),
    Ignored { 
        value: String, 
        used: usize 
    }, // 具名字段结构体变体
}

二、 如何构造与实例化枚举

带数据的枚举变体就像是一个可以传入参数并实例化的工厂或类型:

// 构造一个带有字段的 Ignored 变体实例
let count = WorkerCount::Ignored {
    value: "invalid_env".to_string(),
    used: 4,
};

三、 如何使用与模式匹配(Match)

Rust 强制使用 match 或 if let 对枚举进行穷尽式匹配(Exhaustive Matching),这保证了所有可能的分支都不会被漏掉。

match count {
    WorkerCount::Explicit(n) => {
        println!("使用用户指定的线程数: {n}");
    }
    WorkerCount::Ignored { value, used } => {
        println!("环境变量格式错误: '{}', 已降级使用默认值: {}", value, used);
    }
}

四、 Rust 枚举的两大王牌:Option 与 Result

Rust 标准库中最常用的两个泛型枚举:

  1. Option<T>:用来替代其他语言中的 null。

pub enum Option<T> {
    Some(T),
    None,
}
  1. Result<T, E>:用来处理可恢复的错误。

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

五、 枚举的高级特性与优势

  • 零成本抽象(Zero-cost abstractions):Rust 编译器会对枚举进行内存优化(例如利用指针不为零的特性,让 Option<NonZeroUsize> 的内存大小和原始类型完全一致)。

  • 携带行为:可以通过 impl 块为枚举定义方法、实现特征(Traits)。

  • 类型安全:将状态和与状态相关的数据绑定在一起,使得非法的状态组合在编译期就无法产生。