结构体(Struct)和元组类似,都可以包含不同类型的数据。但与元组不同的是,结构体中的每个字段都有命名,因此更加清晰、灵活。我们不需要依赖数据的顺序来访问值,而是通过字段名来访问。
1. 定义和实例化结构体
1.1 定义结构体
使用 struct 关键字定义结构体,在花括号中列出字段名称和类型:
1 2 3 4 5 6
| struct User { active: bool, username: String, email: String, sign_in_count: u64, }
|
若要让某些结构体或字段在模块外可见,需要加 pub:
1 2 3 4
| pub struct User { pub username: String, email: String, }
|
1.2 创建实例
1 2 3 4 5 6 7 8
| fn main() { let user1 = User { active: true, username: String::from("someusername123"), email: String::from("[email protected]"), sign_in_count: 1, }; }
|
注意: 字段初始化时顺序不需要与定义顺序一致:
1 2 3 4 5 6
| let user1 = User { email: String::from("[email protected]"), active: true, sign_in_count: 1, username: String::from("someusername123"), };
|
1.3 可变结构体
实例默认是不可变的,若要修改字段,需加上 mut:
1 2 3 4 5 6 7 8 9 10 11
| fn main() { let mut user1 = User { active: true, username: String::from("someusername123"), email: String::from("[email protected]"), sign_in_count: 1, };
user1.email = String::from("[email protected]"); user1.sign_in_count += 1; }
|
2. 简化结构体初始化
在函数中返回结构体时,当变量名与字段名相同,可以省略字段名:
1 2 3 4 5 6 7 8
| fn build_user(email: String, username: String) -> User { User { active: true, username, email, sign_in_count: 1, } }
|
3. 基于已有实例创建新实例
3.1 普通写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| fn main() { let user1 = User { active: true, username: String::from("someone"), email: String::from("[email protected]"), sign_in_count: 1, };
let user2 = User { active: user1.active, username: user1.username, email: String::from("[email protected]"), sign_in_count: user1.sign_in_count, }; }
|
3.2 结构体更新语法
Rust 提供了 .. 语法来复用字段:
1 2 3 4 5 6 7 8 9 10 11 12 13
| fn main() { let user1 = User { active: true, username: String::from("someone"), email: String::from("[email protected]"), sign_in_count: 1, };
let user2 = User { email: String::from("[email protected]"), ..user1 }; }
|
⚠️ 注意:
..user1 必须写在最后
- 由于
username 是 String 类型(没有实现 Copy),所有权被转移到 user2
- 创建
user2 后,user1 不能再作为整体使用
user1.active 和 user1.sign_in_count 仍可使用,因为它们实现了 Copy trait
3.3 所有权友好的更新语法示例
1 2 3 4 5 6 7 8 9 10 11 12 13
| #[derive(Debug)] struct Point { x: i32, y: i32, }
fn main() { let p1 = Point { x: 1, y: 2 }; let p2 = Point { x: 3, ..p1 };
println!("{:?}", p1); println!("{:?}", p2); }
|
4. 元组结构体(Tuple Struct)
如果只关心字段类型而不关心字段名,可以使用元组结构体:
1 2 3 4 5 6 7 8 9 10 11
| struct Color(i32, i32, i32); struct Point(i32, i32, i32);
fn main() { let black = Color(0, 0, 0); let origin = Point(0, 0, 0);
println!("红色分量: {}", black.0); println!("x 坐标: {}", origin.0); }
|
注意: 虽然 Color 和 Point 的字段类型相同,但它们是不同的结构体类型,不能互相赋值。
5. 单元结构体(Unit-like Struct)
当结构体不需要任何字段,只关心类型行为时,可以使用单元结构体:
1 2 3 4 5
| struct AlwaysEqual;
fn main() { let subject = AlwaysEqual; }
|
单元结构体常用于实现 trait,但不需要存储数据的场景。
6. 嵌套结构体(Structs 内含 Structs)
结构体字段本身可以是另一个结构体类型,这样可以构造更复杂的数据模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| struct Address { city: String, street: String, zip_code: String, }
struct User { username: String, email: String, address: Address, }
fn main() { let user = User { username: String::from("john_doe"), email: String::from("[email protected]"), address: Address { city: String::from("北京"), street: String::from("长安街1号"), zip_code: String::from("100000"), }, };
println!("用户城市: {}", user.address.city); }
|
7. 调试结构体
默认情况下,println! 不能直接打印结构体。可以通过 #[derive(Debug)] 启用调试格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #[derive(Debug)] struct Rectangle { width: u32, height: u32, }
fn main() { let rect1 = Rectangle { width: 30, height: 50, };
println!("rect1 is {:?}", rect1); println!("rect1 is {:#?}", rect1); }
|
还可以使用 dbg! 宏进行调试,dbg! 会打印源码位置和表达式值,并返回该值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #[derive(Debug)] struct Rectangle { width: u32, height: u32, }
fn main() { let scale = 2; let rect1 = Rectangle { width: dbg!(30 * scale), height: 50, };
dbg!(&rect1); }
|
8. 访问结构体字段
使用点号语法访问结构体字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| fn main() { let user1 = User { active: true, username: String::from("someusername123"), email: String::from("[email protected]"), sign_in_count: 1, };
println!("用户名: {}", user1.username); println!("邮箱: {}", user1.email); println!("活跃状态: {}", user1.active);
let mut user2 = User { active: false, username: String::from("user2"), email: String::from("[email protected]"), sign_in_count: 0, };
user2.active = true; user2.sign_in_count += 1; }
|
9. 方法与关联函数
9.1 方法基础
方法(method) 与函数类似,但有以下特点:
- 定义在
impl 块中
- 第一个参数必须是
self、&self 或 &mut self
- 通过实例调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #[derive(Debug)] struct Rectangle { width: u32, height: u32, }
impl Rectangle { fn area(&self) -> u32 { self.width * self.height }
fn set_width(&mut self, width: u32) { self.width = width; }
fn destroy(self) { println!("销毁矩形 {}x{}", self.width, self.height); }
fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } }
fn main() { let mut rect1 = Rectangle { width: 30, height: 50, };
println!("面积: {}", rect1.area()); rect1.set_width(35);
let rect2 = Rectangle { width: 10, height: 20 }; println!("rect1 能容纳 rect2: {}", rect1.can_hold(&rect2)); }
|
9.2 关联函数(Associated Functions)
关联函数不以 self 作为第一个参数,通常用作构造器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| impl Rectangle { fn new(width: u32, height: u32) -> Rectangle { Rectangle { width, height } }
fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size, } } }
fn main() { let rect = Rectangle::new(10, 20); let sq = Rectangle::square(15); }
|
9.3 多个 impl 块
一个结构体可以有多个 impl 块,这在组织代码或条件编译时很有用。:
1 2 3 4 5 6 7 8 9 10 11
| impl Rectangle { fn area(&self) -> u32 { self.width * self.height } }
impl Rectangle { fn perimeter(&self) -> u32 { 2 * (self.width + self.height) } }
|
总结
结构体是 Rust 中组织相关数据的强大工具。通过合理使用结构体,我们可以:
- 清晰地组织数据:通过命名字段让代码更易读
- 确保类型安全:不同的结构体是不同的类型
- 封装行为:通过方法将数据和操作结合在一起
- 利用所有权系统:确保内存安全和并发安全
掌握结构体的使用是学好 Rust 的重要一步,它为后续学习 trait、泛型等高级特性奠定了基础。
Hooray!结构体学习完成!!!