Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

结构体

在 Rust 中,结构体Structs是构建复杂程序的基石。它允许你将相关联的数据组合在一起,创建出更有意义的自定义类型。相比于元组,结构体为每个数据片段命名,因此更加灵活且意图清晰。


一、 结构体的三种类型

Rust 支持三种不同风格的结构体,分别适用于不同的场景。

1. 具名结构体 (Classic Structs)

最常用的类型,类似于其他语言中的类或对象,每个字段都有明确的名字。

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

fn main() {
    // 实例化结构体
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someuser123"),
        active: true,
        sign_in_count: 1,
    };

    println!("用户 {} 的邮箱是 {}", user1.username, user1.email);
}

关键点:

  • 结构体通常拥有其数据,使用如 String 的拥有类型,以确保数据在结构体存在期间有效。
  • 如果使用引用(如 &str),需要指定生命周期(lifetime),以避免悬垂引用(dangling references)。
  • 字段不能单独标记为可变,整个结构体实例必须是可变的才能修改字段。

2. 元组结构体 (Tuple Structs)

当你想给整个元组起个名字,但不需要为内部每个字段命名时使用。

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

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
  
    // 注意:虽然内部类型一样,但 Color 和 Point 是不同的类型!
    println!("第一个颜色分量: {}", black.0);
}

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

没有任何字段。常用于需要在某个类型上实现 Trait但不需要存储数据的情况。

struct AlwaysEqual;

fn main() {
    let _subject = AlwaysEqual;
}

二、 实例化

1. 字段初始化简写 (Field Init Shorthand)

当变量名与字段名完全相同时,可以简写。

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}
fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username, // 等同于 username: username
        email,    // 等同于 email: email
        sign_in_count: 1,
    }
}

fn main() {
    let user1 = build_user(String::from("a@b.com"), String::from("user1"));
    println!("用户 {} 的邮箱是 {}", user1.username, user1.email);
}

2. 结构体更新语法 (Struct Update Syntax)

当你想要创建一个新实例,但大部分数据与旧实例相同时,使用 .. 语法。

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}
fn main() {
    let user1 = User {
        email: String::from("a@b.com"),
        username: String::from("user1"),
        active: true,
        sign_in_count: 1,
    };

    // 使用 user1 的部分数据创建 user2
    let user2 = User {
        email: String::from("c@d.com"),
        ..user1 // 剩余字段直接拷贝/移动自 user1
    };
    println!("用户 {} 的邮箱是 {}", user2.username, user2.email);
    // println!("用户 {} 的邮箱是 {}", user1.username, user1.email);
    // 注意:由于 String 发生了所有权转移,user1.username 此时已失效!除非那些字段实现了 Copy trait
}

3. 访问和更新字段

使用点号 . 访问字段。要更新,需要可变实例(mut)。

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}
fn main() {
    let mut user1 = User {
        email: String::from("a@b.com"),
        username: String::from("user1"),
        active: true,
        sign_in_count: 1,
    };
    user1.email = String::from("newemail@example.com");
    println!("用户 {} 的邮箱是 {}", user1.username, user1.email);
}

三、 结构体方法:impl

在 Rust 中,数据定义(struct)和行为定义(impl)是分开的。

  • 方法 (Methods) :第一个参数是 self,通过实例调用。
  • 关联函数 (Associated Functions) :没有 self 参数,通过 类型名:: 调用(类似静态方法)。
#[derive(Debug)] // 允许通过 {:?} 打印结构体
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // 关联函数:通常用于构造函数
    fn square(size: u32) -> Self {
        Self { width: size, height: size }
    }

    // 方法:计算面积
    // 使用 &self 借用实例,而不是获取所有权
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // 方法:判断当前矩形是否能容纳另一个矩形
    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::square(10); // 调用关联函数

    println!("矩形面积: {}", rect1.area());
    println!("rect1 能容纳 rect2 吗? {}", rect1.can_hold(&rect2));
    println!("打印结构体详情: {:?}", rect1);
}

关键差异与 OOP

  • Rust 无自动 getter/setter,需要手动定义。
  • 方法名可与字段名相同(基于语法区分)。
  • 强调借用规则,与 OOP 的封装不同。
  • Rust 自动处理引用/解引用,无需 -> 操作符。

四、 结构体与所有权

这是新手最容易困惑的地方:

  1. 字段的所有权 :如果结构体拥有其字段的所有权(如 String),那么当结构体被销毁时,字段也会被销毁。结构体字段若为拥有类型(如 String),实例移动时会转移所有权。
  2. 在结构体中存储引用 :如果你希望结构体存储一个指向外部数据的引用(如 &str),你需要使用 生命周期(Lifetimes) 标注。

目前建议先使用拥有所有权的类型(如 String 而不是 &str),直到学习到生命周期章节。 优先使用借用(&)以避免不必要的移动


总结

特性具名结构体元组结构体单元结构体
访问方式s.field_names.0, s.1不可访问
语义明确的数据对象强类型化的元组标签或特征实现
典型案例用户信息、配置项坐标 (x, y)、颜色 (r, g, b)状态标记、Trait 对象