Rust 智能指针完整详解

17 阅读7分钟

Rust 智能指针完整详解

涵盖:Box、Rc/Arc、RefCell/Mutex、Deref/Drop trait、循环引用等
重点:智能指针选型、线程安全约束、内部可变性、内存泄漏预防


目录

  1. 基础概念
  2. 核心智能指针分类
  3. [Deref 自动解引用(智能指针核心 trait)](#三 deref-自动解引用智能指针核心 trait)
  4. [Drop 析构 trait](#四 drop-析构 trait)
  5. 智能指针选型对照表
  6. 关键使用规则
  7. 高频易错点
  8. 速记口诀

一、基础概念

1. 普通裸指针 *const T / *mut T

无所有权、无自动内存管理、无借用检查,仅存内存地址,操作必须 unsafe

2. 智能指针本质

实现了 Deref + Drop trait 的结构体

  1. Deref:自动解引用,像普通引用一样使用;
  2. Drop:离开作用域自动释放堆内存,防止内存泄漏;
  3. 变量本体存在,栈内只存指针/元数据,真实数据存堆;
  4. 自带所有权、借用、计数、锁等安全逻辑,不用手动管理内存。

区分普通引用 & 智能指针

类型说明
&T借用,不拥有数据,生命周期依附所有者
智能指针拥有堆数据所有权,自行管理内存生命周期

二、核心智能指针分类

独占所有权:Box

底层结构
struct Box<T>(*mut T);

栈上仅一根堆指针,T 完整分配在堆。

核心特性
  1. 唯一所有者,同一时间只能有一个 Box;
  2. 无额外开销,性能等同于裸指针;
  3. 自动 Drop,出作用域释放堆;
  4. 实现 Deref<Target=T>,可直接 . 访问字段。
适用场景
  1. 递归结构体/枚举(解决无限尺寸)
struct Node {
    val: i32,
    next: Option<Box<Node>>
}
  1. 超大栈值,避免栈溢出,转移到堆;
  2. 构造 trait 对象 Box<dyn Trait>(胖指针:数据指针 + vtable);
  3. FFI、需要堆分配的场景。
示例
let b = Box::new(10);
println!("{}", *b); // Deref 解引用
println!("{}", b);  // 自动隐式解引用

单线程共享所有权:Rc / Weak

Rc 底层

堆上分配 RcInner<T>,包含:

  • 强引用计数 strong:持有数据的 Rc 数量;
  • 弱引用计数 weak:Weak 数量;
  • 真实数据 T。
规则
  1. Rc<T> 多份克隆共享同一堆数据,单线程专用,不支持多线程
  2. 每次 .clone() 仅栈拷贝指针,堆上 strong+1;
  3. 所有 Rc 销毁 strong=0,释放数据;weak 不阻止释放;
  4. 内部不可变:Rc 只读,如需修改搭配 RefCell<T>
Weak 弱指针
  1. 不增加强计数,不阻止数据释放;
  2. 调用 upgrade() 尝试转为 Option<Rc<T>>,数据已销毁返回 None;
  3. 解决循环引用内存泄漏。
示例
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 逻辑完全一致,区别:

特性RcArc
计数方式普通整数原子操作 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;
}

实现后支持:

  1. *智能指针 手动解引用;
  2. 自动隐式解引用,直接调用内部字段/方法;
  3. 解引用强制转换:函数传参自动逐层解引用。

示例: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>RefMutMutexGuard 均实现,支持 *mut_ptr = xxx 修改。


四、Drop 析构 trait

所有所有权智能指针都实现 Drop,离开作用域自动执行:

智能指针Drop 行为
Box释放堆上 T
Rc/Arcstrong 计数 -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. 所有权与拷贝

指针行为
Boxmove 语义,赋值转移所有权;clone 深拷贝堆数据
Rc/Arc.clone() 仅增加计数,零堆拷贝
Weak不能直接使用,必须 upgrade 转 Rc/Arc

2. 循环引用泄漏问题

单纯 Rc/Arc 双向引用会造成计数永远不为 0,内存泄漏;

解决:其中一端改用 Weak 弱指针。

3. 线程安全约束 Send / Sync

指针Send/Sync
BoxT 满足 Send/Sync 则 Box 满足
Rc不实现 Send/Sync,禁止跨线程
Arc实现 Send+Sync,可跨线程
MutexSend+Sync

4. 解引用强制多态(函数传参自动转换)

fn read(s: &str) {}
let b = Box::new("test".to_string());
read(&b);
// &Box<String> → &String → &str 自动多层 deref

七、高频易错点

⚠️ 以下易错点需特别注意

  1. Rc 传给 spawn 线程直接编译报错,必须换 Arc;
  2. RefCell 在同一作用域同时存在可变 + 不可变借用,运行时 panic;
  3. 忘记循环引用搭配 Weak,长期运行内存持续上涨;
  4. Mutex lock() 在线程 panic 后锁中毒,后续全部阻塞;
  5. Box::new 超大数组会先在栈构造再拷贝堆,超大数组优先 Vec;
  6. 混淆借用 &T 和智能指针:&无所有权,智能指针拥有堆内存;
  7. Cell 仅支持 Copy 类型,包裹 String 编译报错。

八、速记口诀

智能指针实现 Deref+Drop,栈存指针堆存数据;
Box 独占单所有者,递归 trait 对象必备;
Rc 单线程共享,Arc 多线程原子计数;
Weak 弱引用防循环泄漏,upgrade 转为强指针;
RefCell 单线程内部可变,运行借用检查;
Mutex/RwLock 多线程锁,读写分离性能更佳;
Deref 自动解引用,点号直接访问内部字段;
Rc 禁止跨线程,跨线程共享只用 Arc。