Rust 结构体 (struct) 的详细知识点
作者:菜鸟谢
涵盖:三类形式、实例化语法、内存布局、方法 impl、派生 trait、递归结构体等
特点:结合栈/堆分配详解,含高频易错点总结
一、结构体三类形式
Rust 有三种结构体,用途完全不同:
1. 具名字段结构体(最常用)
每个成员带字段名,适合复杂业务数据:
struct User {
name: String,
age: u32,
is_vip: bool,
}
// 实例化
let u = User {
name: "张三".to_string(),
age: 22,
is_vip: false,
};
// 访问/修改(必须 mut 实例)
println!("{}", u.name);
let mut u2 = User { name: "李四".into(), age: 30, is_vip: true };
u2.age = 31;
2. 元组结构体(无字段名,只用下标访问)
适合简单包装、FFI、newtype 模式:
struct Point(i32, i32);
struct Color(u8, u8, u8);
let p = Point(10, 20);
println!("{} {}", p.0, p.1);
3. 单元结构体(无任何字段)
仅用作标记、trait 实现标记类型:
struct EmptyFlag; let flag = EmptyFlag;
二、实例化简写语法
1. 字段简写(变量名和字段同名)
fn build_user(name: String, age: u32) -> User {
User {
name, // 等价 name: name
age,
is_vip: false,
}
}
2. 结构体更新语法..复用旧实例
只修改部分字段,其余继承原有值:
let u1 = User { name: "A".into(), age: 18, is_vip: false };
let u2 = User { age: 20, ..u1 };
// u2.name = "A", u2.is_vip = false,仅 age 改为 20
⚠️ 所有权注意:..u1 会转移 u1 内堆类型字段所有权(String/Vec),之后不能再使用 u1。
三、内存布局 & 栈/堆分配(重点)
1. 纯值结构体(无任何堆类型字段)
所有成员都是 i32/f64/bool/[T;N] 等值类型:整个结构体完整平铺在栈,内部无指针,无堆内存。
struct Point { x: i32, y: f64 }
let p = Point {x:1, y:2.0}; // 全部栈
2. 结构体包含堆容器字段(String/Vec/Box/Arc 等)
结构体实例本体依然在栈;只是对应字段内部存储指针,真实数据存堆。
struct Student {
id: u64,
name: String, // String 栈存三元指针,字符堆
scores: Vec<u8> // Vec 栈存指针数组,元素堆
}
let s = Student { ... };
// s 整体在栈;name、scores 的真实数据在堆
3. 结构体整体放到堆上
外层套 Box/Rc/Arc/Mutex,栈只存一根指针,结构体本体全部堆分配:
let heap_point: Box<Point> = Box::new(Point{x:0, y:0});
// heap_point 变量栈;Point 结构体存在堆
4. 内存对齐与布局
| 情况 | 说明 |
|---|---|
| 默认 | Rust 不保证字段顺序、对齐和 C 一致,编译器会重排优化内存 |
| #[repr(C)] | 如需和 C 交互固定布局,加此属性 |
#[repr(C)]
struct CPoint {
x: i32,
y: i32,
}
四、结构体方法 impl
1. 关联函数(无 &self,类似构造器)
调用方式:Type::func(),常命名为 new()
impl User {
// 构造器,关联函数
fn new(name: &str, age: u32) -> Self {
Self {
name: name.to_string(),
age,
is_vip: false,
}
}
}
let u = User::new("老王", 25);
2. 实例方法(带 &self / &mut self / self)
| 参数类型 | 含义 |
|---|---|
| &self | 只读借用,不修改结构体 |
| &mut self | 可变借用,修改内部字段 |
| self | 获取所有权,调用后原实例失效 |
impl User {
// 只读方法
fn get_age(&self) -> u32 {
self.age
}
// 修改自身
fn become_vip(&mut self) {
self.is_vip = true;
}
// 拿走所有权
fn consume(self) {
println!("{}", self.name);
}
}
3. 关联常量
impl User {
const MAX_AGE: u32 = 150;
}
println!("{}", User::MAX_AGE);
五、所有权与解构
1. 解构赋值
let User { name, age, is_vip } = u;
// name 拿到 String 所有权,u 失效
只想借用不转移所有权:解构引用
let User { ref name, .. } = &u;
// 等价 let name = &u.name;
2. 字段借用规则
let mut u = User::new("test", 20);
let a = &u.name; // 不可变借用
let b = &mut u.age; // 可变借用
// 同一字段不能同时存在可变 + 不可变借用;不同字段允许
六、派生 trait(derive 常用)
给结构体自动实现标准功能,开发高频:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Point { x: i32, y: i32 }
| Trait | 功能 |
|---|---|
| Debug | 支持 println!("{:?}", p) 打印调试信息 |
| Clone | .clone() 深拷贝实例 |
| Copy | 值可栈拷贝,要求所有字段都实现 Copy(不能含 String/Vec) |
| PartialEq / Eq | 支持 == 相等比较 |
| Hash | 可作为 HashMap key |
| Default | User::default() 快速生成默认值 |
Copy 关键限制:包含 String、Vec、Box 堆类型的结构体无法 derive Copy,只能 Clone。
七、嵌套结构体
结构体字段可以是另一个结构体:
struct Address {
city: String,
street: String,
}
struct User {
name: String,
addr: Address, // 嵌套结构体
}
// 访问
user.addr.city
八、递归结构体(自身包含自己)
直接嵌套会无限尺寸编译报错,必须用 Box 把自身放堆:
// 报错:无限大小
struct Node {
next: Node
}
// 正确,Box 栈存指针,子节点堆分配
struct Node {
val: i32,
next: Option<Box<Node>>
}
九、结构体 vs 元组 vs 枚举 核心区分
| 类型 | 特点 |
|---|---|
| struct | 同时拥有所有字段,描述单一事物完整属性;可命名字段可读性强 |
| tuple | 固定长度、可多类型、无字段名,轻量临时组合 |
| enum | 同一时间仅存一种变体,互斥多种形态 |
十、高频易错点总结
⚠️ 以下易错点需特别注意
- 修改字段必须实例声明 mut,只标记字段 mut 无效
- 结构体更新语法 ..s 会转移堆字段所有权,原变量失效
- 含 String/Vec 的结构体不能 derive Copy,只能 Clone
- 递归结构体内部自引用必须包 Box,否则编译无限尺寸
- 无 #[repr(C)] 时内存字段顺序不固定,不能直接和 C 结构体对等
- 结构体本体默认栈分配;仅外层套 Box/Arc 时整体进堆
- 解构堆类型结构体默认转移所有权,想要借用需要匹配引用 &self
十一、速记口诀
结构三分具名元组单元,new 构造 impl 加方法;
点号访问可 mut 修改,..语法快速复用旧实例;
纯值字段全存栈,字符串 vec 堆上藏;
递归嵌套包 Box,derive 派生 Debug 克隆相等;
C 交互加 repr(C),Copy 仅限纯栈值类型。
到此这篇关于Rust 结构体 (struct) 的详细知识点的文章就介绍到这了,更多相关Rust 结构体内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
