一、结构体概述
结构体(struct)是 Rust 中的核心复合数据结构,用于将多个不同类型的数据组合成一个逻辑整体,实现对现实事物或抽象概念的建模。它与元组的区别在于:
- 元组仅通过位置区分元素,无明确名称,访问依赖索引。
- 结构体为每个字段定义专属名称,无需依赖字段顺序访问,灵活性和可读性更强。
其他编程语言中类似概念包括:JavaScript 的 object、C/C++ 的 struct、Python 的 class(简化数据存储场景)等。
二、结构体基础语法
2.1 定义结构体
结构体定义需包含三个核心部分:关键字 struct、结构体名称、若干带类型的字段。
语法格式
rust
struct 结构体名称 {
字段1: 数据类型1,
字段2: 数据类型2,
// ... 更多字段
}
示例:定义用户结构体
rust
#![allow(unused)] // 允许未使用的变量/结构体,避免编译警告
fn main() {
// 定义存储网站用户信息的结构体
struct User {
active: bool, // 账号是否激活(布尔型)
username: String, // 用户名(字符串类型,拥有所有权)
email: String, // 邮箱(字符串类型,拥有所有权)
sign_in_count: u64 // 登录次数(无符号64位整数)
}
}
2.2 创建结构体实例
创建实例时需为所有字段初始化,字段顺序可与定义时不同。
语法格式
rust
let 实例名称 = 结构体名称 {
字段1: 初始值1,
字段2: 初始值2,
// ... 所有字段需完整初始化
};
示例:创建 User 实例
rust
#![allow(unused)]
fn main() {
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64
}
// 创建用户实例,字段顺序与定义时不同(合法)
let user1 = User {
email: String::from("someone@example.com"), // 邮箱初始化
username: String::from("someusername123"), // 用户名初始化
active: true, // 激活状态初始化
sign_in_count: 1 // 登录次数初始化
};
}
2.3 访问与修改结构体字段
通过 . 操作符 访问结构体字段;若需修改字段值,需将实例声明为 mut(Rust 不支持单独标记某个字段为可变)。
示例:访问与修改字段
rust
#![allow(unused)]
fn main() {
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64
}
// 声明可变实例
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1
};
// 访问 email 字段并修改值
user1.email = String::from("anotheremail@example.com");
println!("修改后的邮箱:{}", user1.email); // 输出:修改后的邮箱:anotheremail@example.com
}
三、结构体创建简化技巧
3.1 字段同名简化初始化
当函数参数与结构体字段名称相同时,可省略"字段名: 参数名"的重复写法,直接使用字段名初始化。
示例:简化构建函数
rust
#![allow(unused)]
fn main() {
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64
}
// 未简化的构建函数:email: email 和 username: username 重复
fn build_user_original(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1
}
}
// 简化后的构建函数:同名字段直接省略赋值符号
fn build_user_simplified(email: String, username: String) -> User {
User {
email, // 等价于 email: email
username, // 等价于 username: username
active: true,
sign_in_count: 1
}
}
// 调用简化构建函数
let user2 = build_user_simplified(
String::from("user2@example.com"),
String::from("user2")
);
}
3.2 结构体更新语法
基于已有结构体实例创建新实例时,可使用 ..已有实例 语法自动复用未显式修改的字段,避免重复赋值。
语法格式
rust
let 新实例 = 结构体名称 {
需修改的字段: 新值,
..已有实例 // 复用已有实例的其他字段(必须放在最后)
};
示例:更新结构体实例
rust
#![allow(unused)]
fn main() {
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64
}
// 已有实例 user1
let user1 = User {
email: String::from("user1@example.com"),
username: String::from("user1"),
active: true,
sign_in_count: 5
};
// 基于 user1 创建 user3,仅修改 email 字段
let user3 = User {
email: String::from("user3@example.com"), // 仅修改邮箱
..user1 // 复用 user1 的 active、username、sign_in_count 字段
};
}
注意:所有权转移与拷贝
更新语法中,字段的所有权行为取决于其类型是否实现 Copy 特征:
- 实现
Copy的类型(如bool、u64、i32):仅拷贝数据,原有实例的字段仍可使用。 - 未实现
Copy的类型(如String、Vec<T>):发生所有权转移,原有实例的该字段不可再使用。
示例验证:
rust
#[derive(Debug)] // 用于打印结构体(后续章节讲解)
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64
}
fn main() {
let user1 = User {
email: String::from("user1@example.com"),
username: String::from("user1"),
active: true,
sign_in_count: 5
};
let user3 = User {
email: String::from("user3@example.com"),
..user1
};
// 合法:active 是 bool 类型(实现 Copy),user1 的 active 未转移
println!("user1 的激活状态:{}", user1.active);
// 报错:username 是 String 类型(未实现 Copy),所有权已转移到 user3
// println!("user1 的用户名:{}", user1.username);
// 报错:user1 的 username 已转移,整个 user1 不可再使用
// println!("user1: {:?}", user1);
}
四、特殊结构体类型
4.1 元组结构体(Tuple Struct)
元组结构体是结构体与元组的结合:有结构体名称,但字段无名称,仅通过位置区分。适用于需要整体标识、但字段含义明确无需命名的场景(如坐标、颜色)。
语法格式
rust
struct 结构体名称(字段类型1, 字段类型2, ...);
示例:定义与使用元组结构体
rust
#![allow(unused)]
fn main() {
// 定义元组结构体:Color(存储 RGB 颜色值)、Point(存储 3D 坐标)
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
// 创建实例:无需指定字段名,按位置传值
let black = Color(0, 0, 0); // 黑色(RGB:0,0,0)
let origin = Point(0, 0, 0); // 3D 坐标系原点(x=0,y=0,z=0)
// 访问字段:通过索引(类似元组)
println!("黑色的 R 通道值:{}", black.0); // 输出:0
println!("原点的 z 坐标:{}", origin.2); // 输出:0
}
注意:不同元组结构体不可混用
即使字段类型完全相同,不同名称的元组结构体仍属于不同类型,不可相互赋值或比较:
rust
#![allow(unused)]
fn main() {
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// 报错:Color 和 Point 是不同类型
// let wrong: Color = origin;
}
4.2 单元结构体(Unit-like Struct)
单元结构体无任何字段,类似 Rust 中的单元类型 ()。适用于仅需类型标识、无需存储数据的场景(如实现特定特征、标记类型)。
语法格式
rust
struct 结构体名称; // 无字段,无括号
示例:定义与使用单元结构体
rust
#![allow(unused)]
fn main() {
// 定义单元结构体:表示"始终相等"的标记类型
struct AlwaysEqual;
// 创建实例:无需初始化字段
let subject = AlwaysEqual;
// 场景:为单元结构体实现特征(后续章节讲解特征时会深入)
trait Equal {
fn is_equal(&self, other: &Self) -> bool;
}
// 实现 Equal 特征:任何 AlwaysEqual 实例都相等
impl Equal for AlwaysEqual {
fn is_equal(&self, other: &Self) -> bool {
true // 始终返回 true
}
}
// 使用特征方法
let a = AlwaysEqual;
let b = AlwaysEqual;
println!("a 和 b 是否相等:{}", a.is_equal(&b)); // 输出:true
}
五、结构体的所有权管理
结构体字段的所有权决定了数据的生命周期和访问权限。默认情况下,结构体应存储拥有所有权的数据 (如 String),而非引用(如 &str),避免生命周期问题。
5.1 存储拥有所有权的数据(推荐)
使用 String、Vec<T> 等拥有所有权的类型作为字段时,结构体完全控制数据的生命周期,无需额外处理借用规则。
示例:正确的所有权设计
rust
#![allow(unused)]
fn main() {
struct User {
username: String, // 拥有所有权,结构体生命周期独立
email: String,
sign_in_count: u64
}
let user = User {
username: String::from("owner_user"),
email: String::from("owner@example.com"),
sign_in_count: 3
};
// 合法:结构体拥有数据所有权,可正常使用
println!("用户名:{}", user.username);
}
5.2 存储引用(需生命周期)
若结构体字段为引用(如 &str),必须通过生命周期(lifetimes) 明确引用的有效范围,否则编译器报错。生命周期的核心作用是确保:结构体的生命周期 ≤ 其引用字段的生命周期。
错误示例:未指定生命周期
rust
// 报错:引用字段缺少生命周期标识
struct User {
username: &str, // 错误:expected named lifetime parameter
email: &str, // 错误:expected named lifetime parameter
sign_in_count: u64
}
fn main() {
let user = User {
username: "error_user",
email: "error@example.com",
sign_in_count: 1
};
}
正确示例:添加生命周期(后续章节详解)
rust
#![allow(unused)]
fn main() {
// 添加生命周期 'a,指定引用字段的生命周期
struct User<'a> {
username: &'a str, // 引用的生命周期为 'a
email: &'a str, // 引用的生命周期为 'a
sign_in_count: u64
}
// 定义字符串字面量(静态生命周期,满足 'a 要求)
let username = "valid_user";
let email = "valid@example.com";
// 创建实例:引用的生命周期 ≥ 结构体生命周期
let user = User {
username,
email,
sign_in_count: 2
};
}
注:生命周期是 Rust 的进阶概念,建议先掌握结构体基础用法,再深入学习。
六、结构体的调试与打印
Rust 默认不支持直接打印结构体(如 println!("{}", struct_instance)),需通过 Debug 特征实现调试打印,或手动实现 Display 特征实现自定义打印。
6.1 使用 #[derive(Debug)] 派生 Debug 特征
Debug 特征用于调试场景,可通过 {:?} 或 {:#?} 格式符打印结构体。#[derive(Debug)] 是 Rust 提供的自动实现 Debug 特征的宏。
示例:基础 Debug 打印
rust
// 派生 Debug 特征,允许调试打印
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50
};
// 使用 {:?} 打印(紧凑格式)
println!("Rectangle 紧凑格式:{:?}", rect);
// 输出:Rectangle 紧凑格式:Rectangle { width: 30, height: 50 }
// 使用 {:#?} 打印(格式化格式,适合复杂结构体)
println!("Rectangle 格式化格式:{:#?}", rect);
// 输出:
// Rectangle 格式化格式:Rectangle {
// width: 30,
// height: 50
// }
}
6.2 使用 dbg! 宏增强调试
dbg! 宏是更强大的调试工具,具有以下特性:
- 打印文件名、行号、表达式及表达式的值。
- 输出到标准错误流(
stderr),而非标准输出流(stdout),避免与正常输出混淆。 - 会返回表达式的所有权,不影响后续代码使用。
示例:使用 dbg! 调试
rust
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32
}
fn main() {
let scale = 2;
// 使用 dbg! 打印表达式 30 * scale 的值,并返回结果赋值给 width
let rect = Rectangle {
width: dbg!(30 * scale), // 调试打印:[src/main.rs:10] 30 * scale = 60
height: 50
};
// 使用 dbg! 打印 rect 的引用(避免所有权转移)
dbg!(&rect); // 调试打印:[src/main.rs:14] &rect = Rectangle { width: 60, height: 50 }
}
输出结果
[src/main.rs:10] 30 * scale = 60
[src/main.rs:14] &rect = Rectangle {
width: 60,
height: 50,
}
6.3 手动实现 Display 特征(自定义打印)
若需更友好的用户级打印格式(如无结构体名称、自定义字段顺序),可手动实现 Display 特征,使用 {} 格式符打印。
示例:实现 Display 特征
rust
use std::fmt; // 导入 fmt 模块,用于实现 Display
// 定义矩形结构体
struct Rectangle {
width: u32,
height: u32
}
// 手动实现 Display 特征
impl fmt::Display for Rectangle {
// f: &mut fmt::Formatter 是格式化输出的写入对象
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// 自定义输出格式:"矩形:宽 30,高 50"
write!(f, "矩形:宽 {}, 高 {}", self.width, self.height)
}
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50
};
// 使用 {} 打印(Display 特征)
println!("{}", rect); // 输出:矩形:宽 30,高 50
}
七、结构体的内存排列
结构体在内存中是连续存储的,但字段的具体排列受内存对齐 影响(Rust 会自动优化内存布局,减少内存碎片)。以 File 结构体为例:
7.1 示例结构体定义
rust
#[derive(Debug)]
struct File {
name: String, // 字符串类型(底层包含 ptr、len、capacity)
data: Vec<u8> // 字节向量(底层包含 ptr、len、capacity)
}
7.2 内存布局解析
| 字段 | 底层组成 | 内存含义 |
|---|---|---|
name |
ptr(指针) |
指向字符串底层 [u8] 数组的内存地址 |
len( usize) |
字符串的长度(字节数) | |
capacity(usize) |
字符串分配的内存容量(字节数,≥ len) | |
data |
ptr(指针) |
指向向量底层 [u8] 数组的内存地址 |
len( usize) |
向量中元素的个数(字节数) | |
capacity(usize) |
向量分配的内存容量(元素个数,≥ len) |
7.3 核心结论
- 结构体字段本身存储在连续内存区域,但字段指向的数据(如
String、Vec<T>的底层数组)存储在堆上,通过指针关联。 - 结构体拥有其所有字段的所有权,若字段指向堆数据(如
String),结构体销毁时会自动释放堆内存(避免内存泄漏)。
八、课后练习与拓展
-
基础练习 :定义一个
Book结构体,包含title(书名,String)、author(作者,String)、pages(页数,u32)、published(是否出版,bool),并创建 2 个实例,使用更新语法基于第一个实例创建第二个实例(修改书名和作者)。 -
进阶练习 :为
Book结构体派生Debug特征,使用dbg!宏调试打印实例;再手动实现Display特征,自定义输出格式(如"《书名》- 作者,共 X 页,出版状态:是/否")。 -
拓展学习 :尝试定义一个元组结构体
Point2D(存储 2D 坐标,f64, f64),实现一个方法计算两个点之间的距离(使用勾股定理:√[(x2-x1)² + (y2-y1)²])。
九、总结
结构体是 Rust 中实现数据抽象和封装的核心工具,主要特性包括:
- 灵活性:支持命名字段,无需依赖字段顺序访问。
- 简化语法:字段同名初始化、更新语法减少重复代码。
- 特殊类型:元组结构体适合简单标识场景,单元结构体适合类型标记场景。
- 所有权安全:默认存储拥有所有权的数据,引用需配合生命周期确保安全。
- 调试友好 :通过
#[derive(Debug)]和dbg!宏快速实现调试打印。