Rust 结构体精要:从基础定义到面向对象实践

在 Rust 的学习中,结构体(Struct)是构建程序的骨干,它承载着对目标问题进行建模和描述的重任。本文基于课程内容,系统性地梳理了 Rust 结构体的定义、形式、所有权特性以及如何通过它来实现面向对象编程的风格。

一、结构体的定义与实例化

结构体是由其他基础类型或复合类型组成的自定义数据类型。在 Rust 中,使用 struct 关键字来定义。

基础示例:

1
2
3
4
5
6
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}

这个 User 结构体由 boolStringu64 等四个字段组成。实例化一个结构体需要同时提供所有字段的值。

便捷的实例化语法:

  1. 字段初始化简写:当变量名与字段名相同时,可以省略字段名。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let active = true;
    let username = String::from("someusername123");
    let email = String::from("someone@example.com");

    let user1 = User {
    active, // 等同于 active: active,
    username, // 等同于 username: username,
    email, // 等同于 email: email,
    sign_in_count: 1,
    };
  2. 结构体更新语法:基于现有实例快速创建新实例,只为不同字段赋值,其余字段自动沿用。

    1
    2
    3
    4
    let user2 = User {
    email: String::from("another@example.com"),
    ..user1
    };

    这种写法在更新数据库记录等场景下非常有用,能让代码保持干净清爽。

二、结构体的三种形式

Rust 的结构体有三种表现形式,以适应不同的使用场景。

  1. 命名结构体:最常用的形式,每个字段都有明确的名字和类型,如上面的 User 示例。
  2. 元组结构体:字段匿名,只有类型名和字段类型。适用于那些需要定义一个新类型,但又不想给每个字段起名字的场景(如 RGB 颜色、三维坐标点)。
    1
    2
    3
    4
    5
    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);

    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
    注意,ColorPoint 虽然内部类型一样,但是是完全不同的类型。
  3. 单元结构体:没有任何字段的结构体。它定义了一个类型,但没有数据,常用于实现某种标记或 trait。
    1
    2
    3
    4
    5
    struct ArticleModule;

    fn main() {
    let module = ArticleModule;
    }

三、结构体中的所有权问题

结构体中的字段可以是所有权类型或引用类型。

  1. 部分移动:当从一个结构体实例中移出某个所有权类型的字段时,该实例的其他字段仍然可用,但这个被移出的字段将无法再被访问,整个实例也无法再被整体使用。
    1
    2
    3
    let email = user1.email; // email 字段的所有权被移动到变量 email
    // println!("{:?}", user1); // 错误!user1 无法再被整体使用
    println!("{}", user1.username); // 正确!其他字段仍可用
  2. 引用类型字段:结构体的字段也可以是引用(如 &str),但这会引入生命周期问题,通常需要更复杂的标注,在日常业务开发中,使用所有权字段基本足够。

四、为结构体赋予“行为” (Impl)

Rust 不是传统的面向对象语言,但可以通过 impl 关键字为结构体(或其他类型)实现方法,从而具备面向对象的特性。

  1. 实例方法:方法的第一个参数是 self(或其变体 &self&mut self),代表调用该方法的实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    impl Rectangle {
    // 通过 &self 不可变借用,只读访问
    fn area(&self) -> u32 {
    self.width * self.height
    }

    // 通过 &mut self,可以在方法内修改实例
    fn scale(&mut self, factor: u32) {
    self.width *= factor;
    self.height *= factor;
    }
    }

    fn main() {
    let mut rect = Rectangle { width: 30, height: 50 };
    println!("Area: {}", rect.area()); // 调用方法
    rect.scale(2);
    }
  2. 关联函数(静态方法):参数列表中没有 self 的函数。它通常用于构造函数,比如约定俗成的 new 函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    impl Rectangle {
    // 关联函数,通常用于构造新实例
    fn new(width: u32, height: u32) -> Self {
    Rectangle { width, height }
    }
    }

    fn main() {
    let rect = Rectangle::new(30, 50); // 使用 :: 语法调用
    }
  3. 方法调用时的自动引用和解引用:Rust 在调用方法时会自动进行引用和解引用,因此直接使用实例、不可变引用或可变引用都可以调用方法。

    1
    2
    3
    4
    5
    6
    let rect1 = Rectangle { width: 30, height: 50 };
    let r1 = &rect1;
    let r2 = &&&rect1; // 不管有多少层引用

    r1.area(); // 自动解引用,正常工作
    r2.area(); // 正常工作

五、便利特性:println!Default

  • #[derive(Debug)]:通过在结构体定义上方添加此属性,可以让结构体实例可以被 println!("{:?}", instance) 打印出来,极大方便了调试。

    1
    2
    #[derive(Debug)]
    struct Rectangle { /* ... */ }
  • Default Trait:为结构体实现 Default trait(通常也通过 #[derive(Default)] 派生),可以创建该类型的默认值(通常各字段取该类型的默认值)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #[derive(Default, Debug)]
    struct Rectangle {
    width: u32,
    height: u32,
    }

    fn main() {
    let rect = Rectangle::default(); // width: 0, height: 0
    // 或者
    let rect: Rectangle = Default::default();
    }

总结

  1. 结构体是自定义类型的核心:它通过 struct 定义,拥有命名、元组和单元三种形式。
  2. 灵活的实例化语法:字段初始化简写和 .. 更新语法让代码更简洁。
  3. 所有权是关键:要理解“部分移动”(Partial Move)的概念。
  4. impl 赋予行为:通过 impl 为结构体实现实例方法(&self)和关联函数(如 new)。
  5. 便利工具:善用 #[derive(Debug)] 进行调试,#[derive(Default)] 快速获取默认实例。

结构体是 Rust 中用户自定义类型的基石。掌握好结构体的使用,是写出健壮、清晰 Rust 代码的重要一步。