Rust 智能指针
Box/Rc/Arc/RefCell 等智能指针如何改变所有权形状:堆分配、共享所有权与内部可变性入口。
#concept
#growing
#dev
#rust
[!info] related notes
Rust 智能指针
这个概念为什么出现
有时默认所有权形状不够:
- 递归结构需要间接(
Box) - 多处共享只读所有权(
Rc/Arc) - 共享下内部可变(
RefCell)
智能指针通常封装所有权或访问管理,常与 Deref、Drop 配合;这不是“同时实现两个 trait”的严格分类。RefCell 是内部可变容器,并不靠直接 Deref 暴露 T,需借用守卫访问。
[!abstract] 一句话理解
Box<T>独享堆分配;Rc/Arc共享所有权(单/多线程);RefCell提供运行时借用检查的内部可变性。
最小可运行示例
场景:配置树节点——堆上递归 + 共享只读配置块
解析器 AST 需要递归 Box;多模块共享同一份只读 Rc 配置快照。
use std::rc::Rc;
// 业务意图:演示 Box 递归与 Rc 共享。
// 教学点:Box 间接;Rc 克隆增加强引用计数。
#[derive(Debug)]
enum Expr {
Num(i32),
Add(Box<Expr>, Box<Expr>),
}
fn main() {
let e = Expr::Add(Box::new(Expr::Num(1)), Box::new(Expr::Num(2)));
println!("{e:?}");
let cfg = Rc::new(String::from("region=cn"));
let cfg2 = Rc::clone(&cfg);
println!("cfg_count~ strong={} values={} / {}", Rc::strong_count(&cfg), cfg, cfg2);
}
建议运行:cargo run
期望输出包含:
Add(Num(1), Num(2))
以及 strong count 为 2 的打印。
结合场景再看三个关注点
- Box 让递归类型大小可知
- Rc::clone 便宜在增计数,不是深拷贝字符串
- 跨线程共享还需满足内部类型的 Send/Sync 约束,不能只把 Rc 改名为 Arc
核心概念与准确模型
| 类型 | 用途 |
|---|---|
Box<T> | 堆分配、递归、大对象转移 |
Rc<T> | 单线程共享所有权 |
Arc<T> | 多线程共享所有权 |
RefCell<T> | 单线程内部可变 |
Mutex<T> | 多线程互斥内部可变 |
组合:Rc<RefCell<T>>、Arc<Mutex<T>> 是常见模式。
设计动机
- 保持安全的前提下扩展所有权图
- RAII 管理计数与锁
- 明确单线程/多线程工具边界
边界与误区
- Rc/Arc 的强引用环会泄漏;Weak 不维持底层 T 存活,upgrade 返回 Option,弱引用并不自动消除所有环
- Arc 仅保证引用计数原子化;Arc
的 Send/Sync 通常要求 T: Send + Sync,Arc<RefCell > 不因此成为线程安全共享方案 - Box
拥有数据但不会保证递归释放深度有界;零大小类型的 Box 不需要实际分配堆空间 RefCell运行时 borrow panic- 不要默认
Arc<Mutex<...>>万事大吉——争用与死锁仍在
[!warning] 常见误区:用 Rc 逃避借用检查 共享所有权有成本与复杂度,优先普通借用。
工程实践
- 能
&T就别Rc - 只有多个线程需要共同持有才用 Arc;独占转移可直接 move,作用域线程也可借用
- 内部可变优先审视设计
- 文档写清不变量
本节总结
- 智能指针改变所有权形状
- Box/Rc/Arc/RefCell 各有场景
- 与并发章节衔接
自测题
Rc为何不是线程安全?Box与&T本质差别?
参考答案
- 引用计数更新非原子,未实现 Send/Sync 的完整保证组合。
- Box 拥有堆数据;&T 只借用已有所有者。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| The Book — Smart Pointers | 官方书 | 智能指针 |
| std::boxed::Box | 标准库 | Box |
| std::rc::Rc | 标准库 | Rc |