Rust: Rust 结构体(Struct):定义、实例化与方法实现
最后更新:2026-08-26
结构体(Struct)是 Rust 中自定义数据类型的核心工具——它把相关数据组合成一个有意义的整体。
如果说变量是"一个数据",那结构体就是"一组相关的数据"。它就像一张表格——每一行是一个实例,每一列是一个字段。
1. 你将学到
- 用
struct关键字定义命名结构体 - 创建结构体实例并访问字段
- 用
impl块添加方法和关联函数 - 元组结构体(Tuple Struct)和单元结构体(Unit Struct)
- 结构体字段的所有权与引用
- 结构体更新语法
..和#[derive(Debug)]
2. 概念图解
flowchart LR
subgraph "struct 定义"
DEF["struct User"]
F1["username: String"]
F2["email: String"]
F3["active: bool"]
F4["sign_in_count: u64"]
end
subgraph "实例化"
INS["let user = User { ... }"]
V1["username: 'alice'"]
V2["email: 'alice@example.com'"]
V3["active: true"]
V4["sign_in_count: 1"]
end
F1 -.->|"字段映射"| V1
F2 -.->|"字段映射"| V2
F3 -.->|"字段映射"| V3
F4 -.->|"字段映射"| V4
3. 一个学生管理系统的故事
(1) 痛苦:用散落的变量管理学生
Maria 是一名老师,她需要管理班级学生的信息。每个学生有:
- 姓名、年龄、年级、GPA
最开始她这样写:
let name1 = "小明";
let age1 = 18;
let grade1 = "高三";
let gpa1 = 3.8;
let name2 = "小红";
let age2 = 17;
let grade2 = "高二";
let gpa2 = 3.5;
当学生数量增加到 40 人时,这些散落的变量根本没法管理。打印成绩单要写 40 行代码,修改一个字段要在 4 个地方改。
(2) Rust 结构体的方案
struct Student {
name: String,
age: u8,
grade: String,
gpa: f64,
}
fn main() {
let student1 = Student {
name: String::from("Xiao Ming"),
age: 18,
grade: String::from("Senior 3"),
gpa: 3.8,
};
let student2 = Student {
name: String::from("Xiao Hong"),
age: 17,
grade: String::from("Senior 2"),
gpa: 3.5,
};
println!("{}: {} 岁, 年级: {}, GPA: {:.1}",
student1.name, student1.age, student1.grade, student1.gpa);
println!("{}: {} 岁, 年级: {}, GPA: {:.1}",
student2.name, student2.age, student2.grade, student2.gpa);
}
结构体像一个"模具"——定义了"学生"应该有哪些信息。每一份实例就是一份填写好的表格。字段通过
.访问,清晰且安全。
4. 结构体类型
graph TB
A[struct 类型] --> B[命名结构体: 字段有名有类型]
A --> C[元组结构体: 字段只有类型没有名字]
A --> D[单元结构体: 没有任何字段]
B --> E[struct User { name: String, age: u8 }]
C --> F[struct Color(i32, i32, i32)]
D --> G[struct EmptyMarker]
| 类型 | 定义方式 | 适用场景 |
|---|---|---|
| 命名结构体 | struct S { f1: T, f2: T } |
大多数场景,字段名自带文档 |
| 元组结构体 | struct S(T1, T2) |
包装单一概念(如颜色 RGB、坐标) |
| 单元结构体 | struct S; |
类型标记、trait 实现占位 |
(2) 方法 vs 关联函数
| 类型 | 第一个参数 | 调用方式 | 所有权 | 典型用途 |
|---|---|---|---|---|
| 方法 | &self |
obj.method() |
借用 | 读取实例数据 |
| 方法 | &mut self |
obj.method() |
可变借用 | 修改实例数据 |
| 方法 | self |
obj.method() |
消费 | 转换/销毁实例 |
| 关联函数 | 无 | Type::function() |
— | 构造函数、工厂方法 |
(3) 常用派生 trait 速查
| Trait | 功能 | 自动条件 | 示例 |
|---|---|---|---|
Debug |
{:?} 格式化打印 |
所有字段实现 Debug | #[derive(Debug)] |
Clone |
.clone() 深拷贝 |
所有字段实现 Clone | #[derive(Clone)] |
Copy |
赋值自动复制 | Clone + 所有字段 Copy | #[derive(Copy, Clone)] |
PartialEq |
== / != 比较 |
所有字段实现 PartialEq | #[derive(PartialEq)] |
Hash |
用作 HashMap 键 | 所有字段实现 Hash | #[derive(Hash)] |
Default |
默认值 | 所有字段有 Default | #[derive(Default)] |
5. 结构体示例
▶ 示例 1:命名结构体的完整用法(难度 ⭐)
// ============================================
// 命名结构体:定义、实例化、字段访问
// ============================================
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
// 创建实例
let rect = Rectangle {
width: 30,
height: 50,
};
// 访问字段
println!("宽度: {}, 高度: {}", rect.width, rect.height);
// 可变实例
let mut mutable_rect = Rectangle {
width: 10,
height: 20,
};
mutable_rect.width = 15; // ✅ 可以修改
println!("修改后宽度: {}", mutable_rect.width);
// 用 Debug 打印
println!("rect: {:?}", rect);
println!("rect (pretty): {:#?}", rect);
}
输出:
宽度: 30, 高度: 50
修改后宽度: 15
rect: Rectangle { width: 30, height: 50 }
rect (pretty): Rectangle {
width: 30,
height: 50,
}
#[derive(Debug)]让结构体可以用{:?}或{:#?}格式化打印。这是调试时最常用的派生 trait 之一。
▶ 示例 2:struct 更新语法与字段简写(难度 ⭐⭐)
// ============================================
// 字段简写初始化 + 更新语法
// ============================================
#[derive(Debug)]
struct User {
username: String,
email: String,
active: bool,
sign_in_count: u64,
}
fn build_user(username: String, email: String) -> User {
User {
username, // 字段名和变量名相同时可简写
email, // 等价于 email: email
active: true,
sign_in_count: 1,
}
}
fn main() {
let user1 = build_user(
String::from("alice"),
String::from("alice@example.com"),
);
// 更新语法:基于 user1 创建 user2,只修改 email 和 username
let user2 = User {
email: String::from("alice_new@example.com"),
username: String::from("alice_new"),
..user1 // 其他字段从 user1 复制(注意:String 字段会 move!)
};
// println!("user1: {}", user1.username); // ❌ username 被 move 到 user2
println!("user2: {:?}", user2);
println!("user1.active: {}", user1.active); // ✅ bool 是 Copy 的,还在
}
输出:
user2: User { username: "alice_new", email: "alice_new@example.com", active: true, sign_in_count: 1 }
user1.active: true
字段简写:当变量名和结构体字段名一致时,可以省略
field: field中的冒号。更新语法..从另一个实例复制剩余字段——注意复制的是 move 语义而不是 Clone。
▶ 示例 3:impl 块——给结构体添加方法(难度 ⭐⭐)
// ============================================
// impl 块:方法(&self)和关联函数(无 &self)
// ============================================
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 方法:&self 是 Rectangle 实例的引用
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 square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 20,
};
let square = Rectangle::square(15); // 关联函数用 :: 调用
println!("rect1 面积: {}", rect1.area()); // 1500
println!("rect1 能否容纳 rect2: {}", rect1.can_hold(&rect2)); // true
println!("正方形面积: {}", square.area()); // 225
}
输出:
rect1 面积: 1500
rect1 能否容纳 rect2: true
正方形面积: 225
方法(Method)的第一个参数是
&self(或&mut self),通过.调用。关联函数(Associated Function)没有&self,通过::调用——经典的例子是String::from()。
▶ 示例 4:元组结构体(难度 ⭐⭐)
// ============================================
// 元组结构体:适合"就是一堆值"的场景
// ============================================
#[derive(Debug)]
struct Color(i32, i32, i32); // RGB 颜色
#[derive(Debug)]
struct Point(i32, i32, i32); // 3D 坐标
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// 字段通过索引访问(像元组)
println!("黑色: R={}, G={}, B={}", black.0, black.1, black.2);
println!("原点: x={}, y={}, z={}", origin.0, origin.1, origin.2);
// 元组结构体和元组结构体不一样!
// let wrong: Color = origin; // ❌ 编译错误:Point 不能赋值给 Color
// 虽然内部结构相同,但它们是不同的类型
// 这防止了"颜色和坐标搞混"的 bug
}
输出:
黑色: R=0, G=0, B=0
原点: x=0, y=0, z=0
元组结构体(Tuple Struct)没有字段名,只有类型。它和元组类似,但有自己的类型名。
Color(0,0,0)和Point(0,0,0)内部相同但类型不同——防止混用。
▶ 示例 5:综合练习——学生管理系统(难度 ⭐⭐⭐)
// ============================================
// 综合示例:结构体方法、关联函数与更新语法
// ============================================
#[derive(Debug, Clone)]
struct Student {
name: String,
age: u8,
scores: Vec<u32>,
}
impl Student {
fn new(name: &str, age: u8) -> Self {
Student {
name: name.to_string(),
age,
scores: Vec::new(),
}
}
fn add_score(&mut self, score: u32) {
self.scores.push(score);
}
fn average(&self) -> f64 {
if self.scores.is_empty() {
return 0.0;
}
let sum: u32 = self.scores.iter().sum();
sum as f64 / self.scores.len() as f64
}
fn grade(&self) -> char {
match self.average() as u32 {
90..=100 => 'A',
80..=89 => 'B',
70..=79 => 'C',
60..=69 => 'D',
_ => 'F',
}
}
fn summary(&self) -> String {
format!("{} ({}岁) 平均: {:.1} 等级: {}", self.name, self.age, self.average(), self.grade())
}
}
fn main() {
let mut alice = Student::new("Alice", 20);
alice.add_score(95);
alice.add_score(88);
alice.add_score(92);
println!("{}", alice.summary());
let mut bob = Student::new("Bob", 21);
bob.add_score(72);
bob.add_score(65);
bob.add_score(58);
println!("{}", bob.summary());
let mut charlie = Student {
name: String::from("Charlie"),
..alice.clone()
};
charlie.name = String::from("Charlie");
charlie.add_score(100);
println!("{}", charlie.summary());
let students = [&alice, &bob, &charlie];
println!("\n=== 班级排名 ===");
let mut ranked: Vec<_> = students.iter().collect();
ranked.sort_by(|a, b| b.average().partial_cmp(&a.average()).unwrap());
for (i, s) in ranked.iter().enumerate() {
println!("第{}名: {}", i + 1, s.summary());
}
}
输出:
Alice (20岁) 平均: 91.7 等级: A
Bob (21岁) 平均: 65.0 等级: D
Charlie (20岁) 平均: 93.8 等级: A
=== 班级排名 ===
第1名: Charlie (20岁) 平均: 93.8 等级: A
第2名: Alice (20岁) 平均: 91.7 等级: A
第3名: Bob (21岁) 平均: 65.0 等级: D
此示例综合运用了关联函数
new()、方法add_score()/average()/grade()、更新语法..alice.clone()和#[derive(Debug, Clone)]。结构体是组织相关数据和方法的核心工具。
❓ 常见问题
📖 小节
- 命名结构体是最常用的结构体形式,字段名清晰
- 元组结构体适合"就是一堆值"的场景(RGB、坐标)
- 单元结构体没有字段,用于类型标记
impl块给结构体添加方法(&self)和关联函数(无&self)- 字段简写初始化:变量名和字段名一致时省略冒号
- 更新语法
..other从一个实例复制剩余字段 #[derive(Debug)]让结构体可打印
📝 作业
- 难度 ⭐:定义一个结构体
Book,包含title: String、author: String、year: u32。创建两个实例并打印标题和作者。 - 难度 ⭐⭐:给
Rectangle添加一个方法fn perimeter(&self) -> u32计算周长。添加一个关联函数fn from_width(w: u32) -> Rectangle创建一个宽高相等的矩形。 - 难度 ⭐⭐⭐:定义一个元组结构体
Distance(f64, f64)表示距离(米、厘米)。实现方法fn to_meters(&self) -> f64返回总米数(如Distance(1, 50)返回1.50)。