Rust 结构体精要:从基础定义到面向对象实践
Rust 结构体精要:从基础定义到面向对象实践
在 Rust 的学习中,结构体(Struct)是构建程序的骨干,它承载着对目标问题进行建模和描述的重任。本文基于课程内容,系统性地梳理了 Rust 结构体的定义、形式、所有权特性以及如何通过它来实现面向对象编程的风格。
一、结构体的定义与实例化
结构体是由其他基础类型或复合类型组成的自定义数据类型。在 Rust 中,使用 struct 关键字来定义。
基础示例:
1 | struct User { |
这个 User 结构体由 bool、String、u64 等四个字段组成。实例化一个结构体需要同时提供所有字段的值。
便捷的实例化语法:
字段初始化简写:当变量名与字段名相同时,可以省略字段名。
1
2
3
4
5
6
7
8
9
10let 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,
};结构体更新语法:基于现有实例快速创建新实例,只为不同字段赋值,其余字段自动沿用。
1
2
3
4let user2 = User {
email: String::from("another@example.com"),
..user1
};这种写法在更新数据库记录等场景下非常有用,能让代码保持干净清爽。
二、结构体的三种形式
Rust 的结构体有三种表现形式,以适应不同的使用场景。
- 命名结构体:最常用的形式,每个字段都有明确的名字和类型,如上面的
User示例。 - 元组结构体:字段匿名,只有类型名和字段类型。适用于那些需要定义一个新类型,但又不想给每个字段起名字的场景(如 RGB 颜色、三维坐标点)。注意,
1
2
3
4
5struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);Color和Point虽然内部类型一样,但是是完全不同的类型。 - 单元结构体:没有任何字段的结构体。它定义了一个类型,但没有数据,常用于实现某种标记或 trait。
1
2
3
4
5struct ArticleModule;
fn main() {
let module = ArticleModule;
}
三、结构体中的所有权问题
结构体中的字段可以是所有权类型或引用类型。
- 部分移动:当从一个结构体实例中移出某个所有权类型的字段时,该实例的其他字段仍然可用,但这个被移出的字段将无法再被访问,整个实例也无法再被整体使用。
1
2
3let email = user1.email; // email 字段的所有权被移动到变量 email
// println!("{:?}", user1); // 错误!user1 无法再被整体使用
println!("{}", user1.username); // 正确!其他字段仍可用 - 引用类型字段:结构体的字段也可以是引用(如
&str),但这会引入生命周期问题,通常需要更复杂的标注,在日常业务开发中,使用所有权字段基本足够。
四、为结构体赋予“行为” (Impl)
Rust 不是传统的面向对象语言,但可以通过 impl 关键字为结构体(或其他类型)实现方法,从而具备面向对象的特性。
实例方法:方法的第一个参数是
self(或其变体&self、&mut self),代表调用该方法的实例。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18impl 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);
}关联函数(静态方法):参数列表中没有
self的函数。它通常用于构造函数,比如约定俗成的new函数。1
2
3
4
5
6
7
8
9
10impl Rectangle {
// 关联函数,通常用于构造新实例
fn new(width: u32, height: u32) -> Self {
Rectangle { width, height }
}
}
fn main() {
let rect = Rectangle::new(30, 50); // 使用 :: 语法调用
}方法调用时的自动引用和解引用:Rust 在调用方法时会自动进行引用和解引用,因此直接使用实例、不可变引用或可变引用都可以调用方法。
1
2
3
4
5
6let rect1 = Rectangle { width: 30, height: 50 };
let r1 = &rect1;
let r2 = &&&rect1; // 不管有多少层引用
r1.area(); // 自动解引用,正常工作
r2.area(); // 正常工作
五、便利特性:println! 与 Default
#[derive(Debug)]:通过在结构体定义上方添加此属性,可以让结构体实例可以被println!("{:?}", instance)打印出来,极大方便了调试。1
2
struct Rectangle { /* ... */ }DefaultTrait:为结构体实现Defaulttrait(通常也通过#[derive(Default)]派生),可以创建该类型的默认值(通常各字段取该类型的默认值)。1
2
3
4
5
6
7
8
9
10
11
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle::default(); // width: 0, height: 0
// 或者
let rect: Rectangle = Default::default();
}
总结
- 结构体是自定义类型的核心:它通过
struct定义,拥有命名、元组和单元三种形式。 - 灵活的实例化语法:字段初始化简写和
..更新语法让代码更简洁。 - 所有权是关键:要理解“部分移动”(Partial Move)的概念。
- 用
impl赋予行为:通过impl为结构体实现实例方法(&self)和关联函数(如new)。 - 便利工具:善用
#[derive(Debug)]进行调试,#[derive(Default)]快速获取默认实例。
结构体是 Rust 中用户自定义类型的基石。掌握好结构体的使用,是写出健壮、清晰 Rust 代码的重要一步。





