在Rust语言中,结构体(struct)是一种自定义复合数据类型,它允许你将多个相关的值打包成一个有意义的组合。与元组(tuple)类似,结构体可以包含多个不同类型的字段,但与元组不同的是,结构体需要明确命名每个字段,这使得代码更具可读性。
定义一个结构体的基本语法如下:
rust复制struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
这个例子定义了一个名为User的结构体,它有四个字段:
username和email是String类型sign_in_count是u64类型active是布尔类型创建结构体实例的语法:
rust复制let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
几点注意事项:
使用点号(.)访问结构体字段:
rust复制println!("User email: {}", user1.email);
如果要修改结构体字段,实例必须是可变的:
rust复制let mut user1 = User { /* ... */ };
user1.email = String::from("newemail@example.com");
注意:Rust不允许只将某个字段标记为可变,要么整个实例可变,要么都不可变。
当变量名与字段名相同时,可以使用简写形式:
rust复制fn build_user(email: String, username: String) -> User {
User {
email, // 等同于 email: email
username, // 等同于 username: username
active: true,
sign_in_count: 1,
}
}
可以使用已有实例的值来创建新实例:
rust复制let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
..user1表示剩余字段使用user1的对应值。这在需要创建多个相似实例时非常有用。
元组结构体(tuple struct)是结构体的另一种形式,它没有命名字段:
rust复制struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
元组结构体适用于需要给元组命名并使其成为独立类型的情况。上面的Color和Point虽然包含相同类型的字段,但它们是不同的类型。
没有任何字段的结构体称为类单元结构体(unit-like struct):
rust复制struct AlwaysEqual;
let subject = AlwaysEqual;
这种结构体常用于需要在某个trait上实现特性但不需要存储数据的情况。
在前面的User结构体定义中,我们使用了String类型而不是&str字符串切片。这意味着User实例拥有其所有数据,只要结构体有效,其数据也有效。
如果要在结构体中存储引用,需要使用生命周期(lifetime)注解:
rust复制struct UserRef<'a> {
username: &'a str,
email: &'a str,
sign_in_count: u64,
active: bool,
}
生命周期确保结构体引用的数据有效性。对于初学者,建议先使用拥有所有权的类型,等熟悉生命周期后再使用引用。
当结构体包含非Copy类型的字段时,赋值会导致移动(move):
rust复制let user1 = User { /* ... */ };
let user2 = user1; // user1被移动,不能再使用
如果结构体的所有字段都实现了Copy trait,则结构体本身也会自动实现Copy trait,此时赋值会进行复制而非移动。
使用impl块为结构体定义方法:
rust复制impl User {
fn is_active(&self) -> bool {
self.active
}
fn increment_sign_in(&mut self) {
self.sign_in_count += 1;
}
}
方法第一个参数总是self,表示方法被调用的结构体实例。&self表示不可变借用,&mut self表示可变借用。
impl块中也可以定义不接收self参数的函数,称为关联函数(associated functions):
rust复制impl User {
fn new(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
}
关联函数通常用于构造器模式。调用时使用::语法:let user = User::new(...);
一个结构体可以有多个impl块,这在组织大型代码库时很有用:
rust复制impl User {
fn is_active(&self) -> bool { /* ... */ }
}
impl User {
fn increment_sign_in(&mut self) { /* ... */ }
}
默认情况下,结构体不能直接打印:
rust复制println!("{:?}", user1); // 错误
要实现调试打印,需要派生Debug trait:
rust复制#[derive(Debug)]
struct User {
// 字段定义...
}
println!("{:?}", user1); // 现在可以工作了
println!("{:#?}", user1); // 更漂亮的打印格式
对于用户友好的输出,可以实现Display trait:
rust复制use std::fmt;
impl fmt::Display for User {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} <{}>", self.username, self.email)
}
}
println!("{}", user1); // 输出: someusername123 <someone@example.com>
结构体可以在match表达式中解构:
rust复制match user1 {
User { username, email, active: true, .. } => {
println!("Active user {} with email {}", username, email);
}
User { active: false, .. } => {
println!("Inactive user");
}
}
..表示忽略剩余字段。模式匹配在处理复杂结构体时非常有用。
命名规范:结构体命名使用大驼峰式(PascalCase),字段命名使用蛇形命名法(snake_case)
字段可见性:默认情况下,结构体字段是私有的。如果需要在模块外访问,需要使用pub关键字:
rust复制pub struct User {
pub username: String,
pub email: String,
sign_in_count: u64, // 仍然是私有的
}
何时使用结构体:
性能考虑:
Box来存储大型字段以减少移动成本测试技巧:
rust复制#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_creation() {
let user = User::new("test@example.com".to_string(), "testuser".to_string());
assert!(user.is_active());
}
}
结构体是Rust中组织数据的基础工具,掌握好结构体的使用是写出高质量Rust代码的关键。在实际项目中,你会频繁使用结构体来建模业务领域,因此理解其各种特性和用法非常重要。