结构体
在 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 自动处理引用/解引用,无需 -> 操作符。
四、 结构体与所有权
这是新手最容易困惑的地方:
- 字段的所有权 :如果结构体拥有其字段的所有权(如
String),那么当结构体被销毁时,字段也会被销毁。结构体字段若为拥有类型(如 String),实例移动时会转移所有权。 - 在结构体中存储引用 :如果你希望结构体存储一个指向外部数据的引用(如
&str),你需要使用 生命周期(Lifetimes) 标注。
目前建议先使用拥有所有权的类型(如
String而不是&str),直到学习到生命周期章节。 优先使用借用(&)以避免不必要的移动
总结
| 特性 | 具名结构体 | 元组结构体 | 单元结构体 |
|---|---|---|---|
| 访问方式 | s.field_name | s.0, s.1 | 不可访问 |
| 语义 | 明确的数据对象 | 强类型化的元组 | 标签或特征实现 |
| 典型案例 | 用户信息、配置项 | 坐标 (x, y)、颜色 (r, g, b) | 状态标记、Trait 对象 |