结构体(struct)是一种自定义数据类型,允许我们将多个相关联的值打包并命名,形成一个有意义的组合。结构体类似于面向对象语言中对象的数据属性。
// 定义结构体
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
// 创建结构体实例
let user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
结构体定义就像一个通用模板,而实例则使用具体数据填充这个模板,创建该类型的值。
使用点表示法访问结构体字段:
let email = user1.email;
如果实例是可变的,可以使用点表示法修改字段:
let mut user1 = User { /* ... */ };
user1.email = String::from("[email protected]");
注意:结构体整体必须是可变的,Rust不允许只将某些特定字段标记为可变。
当参数名与字段名相同时,可以使用字段初始化简写语法:
fn build_user(email: String, username: String) -> User {
User {
active: true,
username, // 等同于 username: username
email, // 等同于 email: email
sign_in_count: 1,
}
}
从另一个实例创建新实例时,可以使用结构体更新语法:
let user2 = User {
email: String::from("[email protected]"),
..user1 // 其余字段使用user1的值
};
这类似于赋值操作,会发生数据移动。对于实现了Copy特性的类型(如u32),会复制值;对于像String这样的类型,所有权会被移动。
元组结构体是类似元组的结构体,有名称但字段没有名称:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
尽管字段类型相同,但Color
和Point
是不同的类型。可以使用索引访问元组结构体的字段(如black.0
)。
没有任何字段的结构体,称为类单元结构体(unit-like struct):
struct AlwaysEqual;
let subject = AlwaysEqual;
这类结构体在需要在某些类型上实现特征(trait)但不需要存储数据时很有用。
结构体定义时可以选择存储所有权类型(如String
)或引用(如&str
):
struct User {
username: String, // 拥有所有权
email: String, // 拥有所有权
// ...
}
选择所有权类型意味着结构体实例拥有其所有数据,且数据在实例有效期间保持有效。
如果需要在结构体中存储引用,必须使用生命周期(lifetime):
struct User<'a> {
username: &'a str,
email: &'a str,
// ...
}
生命周期确保结构体引用的数据在结构体有效时也保持有效。
以下是一个使用结构体计算矩形面积的例子,展示了结构体如何提高代码的清晰度和可维护性:
fn main() {
let width = 30;
let height = 50;
println!("面积是:{}", area(width, height));
}
fn area(width: u32, height: u32) -> u32 {
width * height
}
fn main() {
let rect = (30, 50);
println!("面积是:{}", area(rect));
}
fn area(dimensions: (u32, u32)) -> u32 {
dimensions.0 * dimensions.1
}
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("面积是:{}", area(&rect));
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
使用结构体的优势在于:通过给数据命名增加了含义,参数之间的关系更加明确,代码更易读。
要打印结构体实例进行调试,可以添加Debug
特征:
#[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); // 使用{:#?}美化打印
}
调试信息可以使用println!
宏的{:?}
或{:#?}
占位符,或者使用dbg!
宏。
方法与函数类似,但它们在结构体(或枚举、特征对象)的上下文中定义,并且第一个参数始终是self
。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("面积是:{}", rect.area());
}
使用impl
(实现)块将方法与结构体关联。第一个参数&self
表示方法借用了结构体实例。
方法可以:
&self
&mut self
self
(较少使用)方法可以有多个参数:
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
impl
块中定义的没有self
参数的函数称为关联函数(associated functions):
impl Rectangle {
// 创建正方形的关联函数
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
fn main() {
let sq = Rectangle::square(3);
}
关联函数通常用作构造函数,使用::
语法调用。
每个结构体可以有多个impl
块:
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
结构体让你创建对领域有意义的自定义类型,将相关数据连接在一起并命名各个部分,使代码更加清晰。通过impl
块,可以定义与类型关联的函数,其中方法是一种关联函数,它们指定结构体实例的行为。
结构体为Rust提供了强大的类型系统支持,与枚举一起,成为创建自定义类型的核心工具。
好好学习,天天向上