Rust 结构体与方法
主题:Day 7 - 结构体与方法
第一层:原理层(Why & How)
为什么需要结构体?
在编程中,我们经常需要将相关的数据组合在一起。例如,描述一个用户需要:姓名、年龄、邮箱等多个属性。
传统方式(元组)的问题:
// 元组:可读性差,无法自描述
let user = ("Alice", 30, "alice@example.com");
// 访问方式:user.0, user.1, user.2 - 不直观
结构体解决方案:
// 结构体:字段有名字,语义清晰
struct User {
name: String,
age: u32,
email: String,
}
let user = User {
name: String::from("Alice"),
age: 30,
email: String::from("alice@example.com"),
};
// 访问方式:user.name, user.age, user.email - 一目了然
结构体的内存布局
┌─────────────────────────────────────────┐
│ User 结构体 │
├─────────────────────────────────────────┤
│ name: String │
│ ├── ptr: *u8 ──→ "Alice" (堆) │
│ ├── len: usize = 5 │
│ └── capacity: usize = 5 │
├─────────────────────────────────────────┤
│ age: u32 = 30 │
├─────────────────────────────────────────┤
│ email: String │
│ ├── ptr: *u8 ──→ "alice@..." (堆) │
│ ├── len: usize = 17 │
│ └── capacity: usize = 17 │
└─────────────────────────────────────────┘
内存对齐:结构体字段按最大对齐值对齐
User 总大小 = String(24) + u32(4) + String(24) + padding(4) = 56 bytes
内存布局原则:
- 顺序存储:字段按定义顺序在内存中排列
- 对齐填充:编译器自动插入 padding 以满足对齐要求
- 所有权独立:每个字段拥有独立的所有权
方法的哲学
Rust 的方法设计体现了面向数据编程的思想:
数据(结构体) + 行为(方法) = 完整的抽象
┌─────────────────────────────────────────┐
│ Rectangle 结构体 │
├─────────────────────────────────────────┤
│ 数据 │
│ ├── width: u32 │
│ └── height: u32 │
├─────────────────────────────────────────┤
│ 行为(方法) │
│ ├── area(&self) -> u32 │
│ ├── can_hold(&self, other) -> bool │
│ └── square(size) -> Rectangle │
└─────────────────────────────────────────┘
与其他语言对比:
| 特性 | Rust | Python | Java | C++ |
|---|---|---|---|---|
| 方法定义 | impl 块 | 类内部 | 类内部 | 类内部 |
| self 参数 | 显式 | 隐式 self | 隐式 this | 隐式 this |
| 所有权 | 显式控制 | 引用计数/GC | GC | 手动/智能指针 |
| 静态方法 | Self::method() | @staticmethod | static | static |
self 参数的本质
impl Rectangle {
// &self 实际上是 &Rectangle 的语法糖
fn area(&self) -> u32 {
self.width * self.height
}
// 等价于:
// fn area(self: &Rectangle) -> u32
}
三种 self 形式:
| 形式 | 含义 | 使用场景 |
|---|---|---|
self | 获取所有权 | 方法消耗实例 |
&self | 不可变借用 | 只读访问 |
&mut self | 可变借用 | 修改实例 |
第二层:实战层(What & Do)
1. 定义结构体
具名结构体(Named Struct)
// 定义
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// 创建实例
let user1 = User {
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
active: true,
};
// 访问字段
println!("用户名: {}", user1.username);
println!("邮箱: {}", user1.email);
元组结构体(Tuple Struct)
// 没有字段名,只有类型
struct Point(i32, i32, i32);
struct Color(i32, i32, i32);
let origin = Point(0, 0, 0);
let black = Color(0, 0, 0);
// 访问:按索引
println!("x: {}", origin.0);
println!("y: {}", origin.1);
用途:
- 给元组起个有意义的名字
- 创建不同的类型(Point 和 Color 是不同的类型)
单元结构体(Unit Struct)
// 没有任何字段
struct AlwaysEqual;
let subject = AlwaysEqual;
用途:
- 标记类型
- 实现 trait 时不需要存储数据
2. 结构体更新语法
let user1 = User {
username: String::from("user1"),
email: String::from("user1@example.com"),
sign_in_count: 1,
active: true,
};
// 基于 user1 创建 user2,只修改 email
let user2 = User {
email: String::from("user2@example.com"),
..user1 // 其余字段从 user1 复制
};
// 注意:username 发生了移动,user1 不能再使用
// println!("{}", user1.username); // 错误!
3. 定义方法
struct Rectangle {
width: u32,
height: u32,
}
// impl 块:实现结构体的方法
impl Rectangle {
// 方法:第一个参数是 self
fn area(&self) -> u32 {
self.width * self.height
}
// 方法:可以访问其他方法
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// 可变方法:修改 self
fn scale(&mut self, factor: u32) {
self.width *= factor;
self.height *= factor;
}
}
// 使用
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("面积: {}", rect1.area()); // 自动解引用为 &Rectangle
let rect2 = Rectangle {
width: 10,
height: 40,
};
println!("rect1 能容纳 rect2? {}", rect1.can_hold(&rect2));
}
4. 关联函数(静态方法)
impl Rectangle {
// 关联函数:没有 self 参数
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
// 构造函数也是关联函数
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
}
// 使用 :: 调用
let sq = Rectangle::square(3);
let rect = Rectangle::new(10, 20);
调用语法对比:
| 类型 | 调用方式 | 示例 |
|---|---|---|
| 方法 | 对象.方法() | rect.area() |
| 关联函数 | 类型::函数() | Rectangle::square(3) |
5. 多个 impl 块
struct Rectangle {
width: u32,
height: u32,
}
// impl 块 1
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
// impl 块 2(可以分开,通常用于 trait 实现)
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
用途:
- 组织代码
- 为泛型实现不同约束的方法
6. 结构体与所有权
struct User {
name: String, // 拥有所有权
email: String, // 拥有所有权
age: u32, // Copy 类型,直接复制
}
fn main() {
let user = User {
name: String::from("Alice"),
email: String::from("alice@example.com"),
age: 30,
};
// 移动整个结构体
let user2 = user;
// println!("{}", user.name); // 错误!user 已被移动
// 借用
let user3 = User {
name: String::from("Bob"),
email: String::from("bob@example.com"),
age: 25,
};
print_user(&user3); // 借用
println!("{}", user3.name); // 仍然可用
}
fn print_user(user: &User) {
println!("{} - {}", user.name, user.email);
}
7. 派生 trait
// 自动派生 Debug trait,支持 {:?} 打印
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
// 自动派生多个 trait
#[derive(Debug, Clone, Copy, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("{:?}", rect); // Rectangle { width: 30, height: 50 }
println!("{:#?}", rect); // 美化打印
}
常用可派生 trait:
| Trait | 功能 | 要求 |
|---|---|---|
Debug | 调试输出 {:?} | 所有字段实现 Debug |
Clone | 显式克隆 .clone() | 所有字段实现 Clone |
Copy | 隐式复制 | 所有字段是 Copy 类型 |
PartialEq | 相等比较 == | 所有字段实现 PartialEq |
Eq | 完全相等 | 满足 PartialEq + 自反性 |
PartialOrd | 部分排序 | 所有字段实现 PartialOrd |
Ord | 完全排序 | 满足 PartialOrd + 全序 |
Hash | 哈希计算 | 所有字段实现 Hash |
Default | 默认值 Default::default() | 所有字段实现 Default |
第三层:最佳实践(Production Ready)
1. 构造函数模式
struct Config {
host: String,
port: u16,
timeout: Duration,
}
impl Config {
// 默认构造函数
fn new(host: &str, port: u16) -> Self {
Self {
host: host.to_string(),
port,
timeout: Duration::from_secs(30),
}
}
// Builder 模式
fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
}
// 使用
let config = Config::new("localhost", 8080)
.with_timeout(Duration::from_secs(60));
2. 使用 &str 替代 String
// ❌ 不推荐:强制拥有所有权
struct Config {
name: String,
}
// ✅ 推荐:使用引用,更灵活
struct Config<'a> {
name: &'a str,
}
// 或者使用 Cow(Clone on Write)
use std::borrow::Cow;
struct Config<'a> {
name: Cow<'a, str>,
}
3. 使用 Option 处理可选字段
struct User {
name: String,
email: String,
phone: Option<String>, // 可选
address: Option<String>, // 可选
age: Option<u32>, // 可选
}
impl User {
fn new(name: &str, email: &str) -> Self {
Self {
name: name.to_string(),
email: email.to_string(),
phone: None,
address: None,
age: None,
}
}
}
4. 使用类型别名增强语义
// ❌ 不推荐:魔法数字
type Point = (i32, i32, i32);
// ✅ 推荐:命名结构体
struct Point {
x: i32,
y: i32,
z: i32,
}
// 或者使用 newtype 模式
type UserId = u64;
type OrderId = u64;
// 更好的方式:使用结构体包装
struct UserId(u64);
struct OrderId(u64);
// 这样 UserId 和 OrderId 是不同的类型,不能混淆
5. 实现 Display trait
use std::fmt;
struct Point {
x: i32,
y: i32,
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
// 使用
let p = Point { x: 1, y: 2 };
println!("{}", p); // 输出: (1, 2)
6. 避免过度使用结构体更新语法
// ❌ 不推荐:可能产生意外的所有权转移
let user2 = User {
email: String::from("new@example.com"),
..user1
};
// user1 的某些字段可能已被移动
// ✅ 推荐:显式复制需要的字段
let user2 = User {
name: user1.name.clone(),
email: String::from("new@example.com"),
age: user1.age,
active: user1.active,
};
// user1 仍然有效
7. 使用 derive 宏
// ✅ 推荐:自动派生常用 trait
#[derive(Debug, Clone, PartialEq, Default)]
struct Rectangle {
width: u32,
height: u32,
}
// 使用 Default
let rect: Rectangle = Default::default(); // width=0, height=0
// 使用 PartialEq
assert_eq!(rect, Rectangle { width: 0, height: 0 });
第四层:问题诊断(Troubleshooting)
问题 1:结构体字段所有权转移
症状:使用结构体更新语法后,原结构体无法访问
let user1 = User {
name: String::from("Alice"),
email: String::from("alice@example.com"),
age: 30,
};
let user2 = User {
email: String::from("bob@example.com"),
..user1
};
println!("{}", user1.name); // 错误!name 已被移动到 user2
诊断:
String类型没有实现Copytrait- 结构体更新语法会移动未显式指定的字段
解决方案:
// 方案 1:显式 clone
let user2 = User {
name: user1.name.clone(),
email: String::from("bob@example.com"),
age: user1.age,
};
// 方案 2:使用引用
struct User<'a> {
name: &'a str,
email: &'a str,
age: u32,
}
问题 2:方法调用时所有权错误
症状:方法调用后无法继续使用实例
impl Rectangle {
// ❌ 错误:获取了所有权
fn consume(self) {
println!("消费了 {:?}", self);
}
}
let rect = Rectangle { width: 10, height: 20 };
rect.consume();
rect.area(); // 错误!rect 已被消费
解决方案:
impl Rectangle {
// ✅ 正确:使用 &self
fn area(&self) -> u32 {
self.width * self.height
}
}
let rect = Rectangle { width: 10, height: 20 };
rect.area();
rect.area(); // 可以多次调用
问题 3:可变借用与不可变借用冲突
症状:编译错误 "cannot borrow as mutable because it is also borrowed as immutable"
let mut rect = Rectangle { width: 10, height: 20 };
let area = rect.area(); // 不可变借用 &self
rect.scale(2); // 可变借用 &mut self
println!("{}", area); // 错误!area 仍然借用 rect
解决方案:
let mut rect = Rectangle { width: 10, height: 20 };
let area = rect.area();
println!("{}", area); // 先使用完不可变借用
rect.scale(2); // 再进行可变借用
问题 4:结构体递归定义
症状:编译错误 "has infinite size"
// ❌ 错误:递归定义
struct Node {
value: i32,
next: Node, // 错误!Node 包含自己
}
解决方案:
// ✅ 正确:使用 Box 间接引用
struct Node {
value: i32,
next: Option<Box<Node>>, // Box 在堆上分配
}
// 或者使用引用(需要生命周期)
struct Node<'a> {
value: i32,
next: Option<&'a Node<'a>>,
}
问题 5:derive 宏失败
症状:编译错误 "the trait bound is not satisfied"
#[derive(Clone)]
struct User {
name: String,
data: Vec<u8>,
// 某个字段没有实现 Clone
}
诊断:
- 所有字段必须实现对应的 trait
- 检查每个字段的类型
解决方案:
// 手动实现 Clone
impl Clone for User {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
data: self.data.clone(),
}
}
}
// 或者跳过某些字段
#[derive(Clone)]
struct User {
name: String,
#[allow(dead_code)]
data: Vec<u8>, // 手动处理
}
第五层:权威引用(References)
官方文档
- The Rust Programming Language - 结构体 - 官方教程结构体章节
- The Rust Reference - Struct Types - 结构体类型参考
- Rust By Example - 结构体 - 示例驱动学习
标准库文档
- std::fmt::Display - 格式化输出 trait
- std::clone::Clone - 克隆 trait
- std::marker::Copy - 复制 trait
- std::default::Default - 默认值 trait
社区资源
- Rust API Guidelines - 结构体命名 - API 命名规范
- The Rust Programming Language - 方法语法 - 方法详解
- Rust Design Patterns - Builder Pattern - 建造者模式
推荐书籍
- 《The Rust Programming Language》(官方教程)
- 《Programming Rust》(O'Reilly)
- 《Rust in Action》
总结
| 概念 | 说明 | 示例 |
|---|---|---|
| 具名结构体 | 带字段名的自定义类型 | struct User { name: String } |
| 元组结构体 | 无字段名的结构体 | struct Point(i32, i32) |
| 单元结构体 | 无字段的结构体 | struct Empty; |
| 方法 | 第一个参数是 self | fn area(&self) -> u32 |
| 关联函数 | 无 self 参数 | fn square(size: u32) -> Self |
| derive | 自动实现 trait | #[derive(Debug, Clone)] |