5.5 结构体(struct)与实例化
结构体(struct)是 Rust 中自定义数据类型的一种核心方式,它允许你将多个相关的值组合成一个有意义的整体。可以把结构体想象成一个“蓝图”或“模板”,它定义了数据应该是什么样子的。通过实例化结构体,你可以创建具体的“实例”来存储实际的数据。
5.5.1 定义结构体
在 Rust 中,使用 struct 关键字来定义结构体。结构体的名称通常采用大驼峰命名法(PascalCase)。结构体内部包含多个“字段”(fields),每个字段都有一个名称和类型。
基本语法:
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
在这个例子中,我们定义了一个名为 User 的结构体,它包含四个字段:
username: 一个String类型的用户名。email: 一个String类型的电子邮件地址。sign_in_count: 一个u64类型的登录次数。active: 一个bool类型的状态,表示用户是否活跃。
5.5.2 创建结构体实例
定义好结构体后,就可以创建它的实例了。创建实例时,需要为所有字段提供具体的值。字段的顺序可以与定义时的顺序不同。
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
注意: 整个实例必须是可变的,Rust 不支持只让结构体的某些字段可变。如果要修改字段的值,需要在实例前加上 mut 关键字。
let mut user2 = User {
email: String::from("another@example.com"),
username: String::from("anotheruser456"),
active: false,
sign_in_count: 0,
};
user2.email = String::from("newemail@example.com"); // 可以修改
5.5.3 访问结构体字段
使用点号(.)来访问结构体实例的字段。
println!("User email: {}", user1.email);
println!("User is active: {}", user1.active);
5.5.4 字段初始化简写
当函数参数名与结构体字段名完全相同时,可以使用字段初始化简写(Field Init Shorthand)来简化代码。
fn build_user(email: String, username: String) -> User {
User {
email, // 等价于 email: email
username, // 等价于 username: username
active: true,
sign_in_count: 1,
}
}
5.5.5 结构体更新语法
你可以基于一个已有的结构体实例,快速创建一个新的实例,并只修改其中部分字段。这被称为结构体更新语法(Struct Update Syntax),使用 .. 操作符。
let user3 = User {
email: String::from("third@example.com"),
username: String::from("thirduser789"),
..user1 // 从 user1 中获取 active 和 sign_in_count 字段的值
};
重要提示: 使用 .. 时,如果被复制的字段(如 user1 的 username 和 email)是实现了 Copy trait 的类型(例如整数、布尔值),则 user1 仍然可用。但如果字段是所有权类型(如 String),则 user1 中的 username 和 email 的所有权会被移动到 user3 中,之后 user1 将不再有效(除非你显式地克隆它们)。
5.5.6 元组结构体 (Tuple Structs)
元组结构体是一种特殊形式的结构体,它的字段没有名称,只有类型。它看起来像是一个命名的元组,非常适合用来创建不同类型的新类型(newtype)。
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
尽管 Color 和 Point 内部都由三个 i32 组成,但它们是不同的类型。你可以通过索引来访问元组结构体的字段。
println!("Red component: {}", black.0);
5.5.7 单元结构体 (Unit-Like Structs)
单元结构体没有任何字段。它们通常用于在某个类型上实现 trait,但不需要存储任何数据。
struct AlwaysEqual;
let subject = AlwaysEqual;
5.5.8 结构体的所有权
结构体的字段可以拥有数据的所有权,也可以持有引用(通过生命周期)。但默认情况下,结构体实例拥有其字段的所有权。当结构体实例被丢弃时,其所有字段也会被丢弃。
struct User {
username: String, // 拥有 String 的所有权
// ...
}
5.5.9 打印结构体
结构体默认没有实现 Display trait,因此不能直接用 {} 格式化打印。但你可以通过添加 #[derive(Debug)] 属性来让结构体实现 Debug trait,然后使用 {:?} 或 {:#?} 来打印。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("rect is {:?}", rect); // 单行打印
println!("rect is {:#?}", rect); // 多行漂亮打印
}
输出示例:
rect is Rectangle { width: 30, height: 50 }
rect is Rectangle {
width: 30,
height: 50,
}
5.5.10 结构体的方法
结构体不仅可以存储数据,还可以通过 impl 块来关联函数(方法)。方法的第一个参数总是 self(或其变体 &self、&mut self),它代表调用该方法的实例。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 计算面积的方法
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 {
width: 10,
height: 40,
};
println!("The area of rect1 is {} square pixels.", rect1.area());
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
}
5.5.11 关联函数
在 impl 块中,不带 self 参数的函数被称为“关联函数”(associated functions)。它们通常用于创建新实例的构造函数,例如 String::from()。
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let sq = Rectangle::square(3); // 使用 :: 语法调用关联函数
}
总结
结构体是 Rust 中组织和封装数据的基石。通过定义结构体、创建实例、使用字段和方法,你可以构建出清晰、可维护且类型安全的程序。掌握结构体的定义、实例化、更新语法以及方法,是深入学习 Rust 的重要一步。
