Rust —— 结构体

结构体(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, // 等价于 username: username
email, // 等价于 email: 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, // String 没有实现 Copy,所有权被移动
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 必须写在最后
  • 由于 usernameString 类型(没有实现 Copy),所有权被转移到 user2
  • 创建 user2 后,user1 不能再作为整体使用
  • user1.activeuser1.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, // i32 实现了 Copy
y: i32, // i32 实现了 Copy
}

fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 3, ..p1 }; // p1 仍然可用,因为所有字段都实现了 Copy

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);
}

注意: 虽然 ColorPoint 的字段类型相同,但它们是不同的结构体类型,不能互相赋值。

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);

// 修改字段(需要 mut)
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 {
// &self: 借用实例,不获取所有权
fn area(&self) -> u32 {
self.width * self.height
}

// &mut self: 可变借用,可以修改实例
fn set_width(&mut self, width: u32) {
self.width = width;
}

// self: 获取所有权,方法调用后实例不可再用
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 中组织相关数据的强大工具。通过合理使用结构体,我们可以:

  1. 清晰地组织数据:通过命名字段让代码更易读
  2. 确保类型安全:不同的结构体是不同的类型
  3. 封装行为:通过方法将数据和操作结合在一起
  4. 利用所有权系统:确保内存安全和并发安全

掌握结构体的使用是学好 Rust 的重要一步,它为后续学习 trait、泛型等高级特性奠定了基础。

Hooray!结构体学习完成!!!