Rust 智能指针完整详解
涵盖:Box、Rc/Arc、RefCell/Mutex、Deref/Drop trait、循环引用等
重点:智能指针选型、线程安全约束、内部可变性、内存泄漏预防
目录
- 基础概念
- 核心智能指针分类
- [Deref 自动解引用(智能指针核心 trait)](#三 deref-自动解引用智能指针核心 trait)
- [Drop 析构 trait](#四 drop-析构 trait)
- 智能指针选型对照表
- 关键使用规则
- 高频易错点
- 速记口诀
一、基础概念
1. 普通裸指针 *const T / *mut T
无所有权、无自动内存管理、无借用检查,仅存内存地址,操作必须 unsafe。
2. 智能指针本质
实现了 Deref + Drop trait 的结构体
Deref:自动解引用,像普通引用一样使用;Drop:离开作用域自动释放堆内存,防止内存泄漏;- 变量本体存在栈,栈内只存指针/元数据,真实数据存堆;
- 自带所有权、借用、计数、锁等安全逻辑,不用手动管理内存。
区分普通引用 & 智能指针
| 类型 | 说明 |
|---|---|
&T | 借用,不拥有数据,生命周期依附所有者 |
| 智能指针 | 拥有堆数据所有权,自行管理内存生命周期 |
二、核心智能指针分类
独占所有权:Box
底层结构
struct Box<T>(*mut T);
栈上仅一根堆指针,T 完整分配在堆。
核心特性
- 唯一所有者,同一时间只能有一个 Box;
- 无额外开销,性能等同于裸指针;
- 自动 Drop,出作用域释放堆;
- 实现
Deref<Target=T>,可直接.访问字段。
适用场景
- 递归结构体/枚举(解决无限尺寸)
struct Node {
val: i32,
next: Option<Box<Node>>
}
- 超大栈值,避免栈溢出,转移到堆;
- 构造 trait 对象
Box<dyn Trait>(胖指针:数据指针 + vtable); - FFI、需要堆分配的场景。
示例
let b = Box::new(10);
println!("{}", *b); // Deref 解引用
println!("{}", b); // 自动隐式解引用
单线程共享所有权:Rc / Weak
Rc 底层
堆上分配 RcInner<T>,包含:
- 强引用计数 strong:持有数据的 Rc 数量;
- 弱引用计数 weak:Weak 数量;
- 真实数据 T。
规则
Rc<T>多份克隆共享同一堆数据,单线程专用,不支持多线程;- 每次
.clone()仅栈拷贝指针,堆上 strong+1; - 所有 Rc 销毁 strong=0,释放数据;weak 不阻止释放;
- 内部不可变:
Rc只读,如需修改搭配RefCell<T>。
Weak 弱指针
- 不增加强计数,不阻止数据释放;
- 调用
upgrade()尝试转为Option<Rc<T>>,数据已销毁返回 None; - 解决循环引用内存泄漏。
示例
use std::rc::Rc;
let a = Rc::new("hello".to_string());
let b = Rc::clone(&a); // 仅计数 +1,不拷贝字符串
println!("strong count: {}", Rc::strong_count(&a));
多线程共享所有权:Arc / Weak
和 Rc 逻辑完全一致,区别:
| 特性 | Rc | Arc |
|---|---|---|
| 计数方式 | 普通整数 | 原子操作 AtomicUsize |
| 线程安全 | ❌ 单线程 | ✅ 跨线程 |
| 性能开销 | 低 | 略高(原子指令) |
搭配内部可变性:Arc<Mutex<T>> / Arc<RwLock<T>>
内部可变性容器(栈存指针,堆存标记 + 数据)
1. RefCell 单线程内部可变性
突破外部不可变限制,运行时借用检查(而非编译期)
- 存储:堆存数据 + 借用状态标记(借出数量、是否可变)
- 方法:
.borrow()→Ref<T>不可变借用(可多个同时存在).borrow_mut()→RefMut<T>可变借用(同一时间只能一个)
- 违反规则运行时 panic,非编译报错。
搭配 Rc 实现单线程共享可变数据:Rc<RefCell<T>>
use std::rc::Rc;
use std::cell::RefCell;
let val = Rc::new(RefCell::new(0));
*val.borrow_mut() += 1;
2. Cell
内部可变性,只适合 Copy 小类型
- 小 Copy 类型直接栈存储,无堆分配;
- 无借用,直接
.get()/.set(),不会触发 panic; - 不能包裹 String、Vec 非 Copy 类型。
3. Mutex 互斥锁(多线程)
多线程内部可变性,同一时间仅一个线程访问数据:
.lock()获取锁,返回MutexGuard<T>,阻塞等待;- 中毒:持有锁线程 panic,锁永久失效;
- 搭配 Arc:
Arc<Mutex<T>>多线程共享可变资源。
4. RwLock 读写锁
- 多个读共存,写独占;读多写少场景性能优于 Mutex。
三、Deref 自动解引用(智能指针核心 trait)
Deref trait 定义
trait Deref {
type Target;
fn deref(&self) -> &Self::Target;
}
实现后支持:
*智能指针手动解引用;- 自动隐式解引用,直接调用内部字段/方法;
- 解引用强制转换:函数传参自动逐层解引用。
示例:Box 自动解引用
struct Point { x: i32, y: i32 }
let p = Box::new(Point { x: 1, y: 2 });
println!("{}", p.x); // 自动 deref,无需 *p
DerefMut 可变解引用
trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Box<T>、RefMut、MutexGuard 均实现,支持 *mut_ptr = xxx 修改。
四、Drop 析构 trait
所有所有权智能指针都实现 Drop,离开作用域自动执行:
| 智能指针 | Drop 行为 |
|---|---|
| Box | 释放堆上 T |
| Rc/Arc | strong 计数 -1,计数归零释放堆 |
| RefCell/Mutex | 释放内部堆数据 |
手动提前释放:std::mem::drop(x),主动转移所有权触发 Drop。
五、智能指针选型对照表
| 指针 | 线程安全 | 所有权模式 | 可变性方案 | 典型用途 |
|---|---|---|---|---|
| Box | 单/多线程均可 | 独占唯一 | 外部 mut | 递归类型、堆分配、trait 对象 |
| Rc | ❌ 单线程 | 共享多所有者 | Rc<RefCell> | 单线程多层共享 |
| Arc | ✅ 多线程 | 共享多所有者 | Arc<Mutex/RwLock> | 跨线程共享资源 |
| RefCell | ❌ 单线程 | 独占 | 运行时借用检查 | 单线程内部可变性 |
| Mutex | ✅ 多线程 | 独占 | 互斥排他访问 | 多线程可变共享 |
| RwLock | ✅ 多线程 | 独占 | 读共享写排他 | 读多写少并发 |
| Weak | 跟随 Rc/Arc | 弱引用不计数 | 无 | 解决循环引用 |
六、关键使用规则
1. 所有权与拷贝
| 指针 | 行为 |
|---|---|
| Box | move 语义,赋值转移所有权;clone 深拷贝堆数据 |
| Rc/Arc | .clone() 仅增加计数,零堆拷贝 |
| Weak | 不能直接使用,必须 upgrade 转 Rc/Arc |
2. 循环引用泄漏问题
单纯 Rc/Arc 双向引用会造成计数永远不为 0,内存泄漏;
解决:其中一端改用 Weak 弱指针。
3. 线程安全约束 Send / Sync
| 指针 | Send/Sync |
|---|---|
| Box | T 满足 Send/Sync 则 Box 满足 |
| Rc | 不实现 Send/Sync,禁止跨线程 |
| Arc | 实现 Send+Sync,可跨线程 |
| Mutex | Send+Sync |
4. 解引用强制多态(函数传参自动转换)
fn read(s: &str) {}
let b = Box::new("test".to_string());
read(&b);
// &Box<String> → &String → &str 自动多层 deref
七、高频易错点
⚠️ 以下易错点需特别注意
- Rc 传给 spawn 线程直接编译报错,必须换 Arc;
- RefCell 在同一作用域同时存在可变 + 不可变借用,运行时 panic;
- 忘记循环引用搭配 Weak,长期运行内存持续上涨;
- Mutex lock() 在线程 panic 后锁中毒,后续全部阻塞;
- Box::new 超大数组会先在栈构造再拷贝堆,超大数组优先 Vec;
- 混淆借用
&T和智能指针:&无所有权,智能指针拥有堆内存; - Cell 仅支持 Copy 类型,包裹 String 编译报错。
八、速记口诀
智能指针实现 Deref+Drop,栈存指针堆存数据;
Box 独占单所有者,递归 trait 对象必备;
Rc 单线程共享,Arc 多线程原子计数;
Weak 弱引用防循环泄漏,upgrade 转为强指针;
RefCell 单线程内部可变,运行借用检查;
Mutex/RwLock 多线程锁,读写分离性能更佳;
Deref 自动解引用,点号直接访问内部字段;
Rc 禁止跨线程,跨线程共享只用 Arc。