12
智能指针:Box / Rc / Arc / RefCell
Smart Pointers · Interior Mutability
智能指针是"比普通引用多一点元数据和能力"的类型,最典型就是 Box<T>、Rc<T>、Arc<T>、RefCell<T>。它们解决"所有权规则下不好表达"的场景。
Box<T>:把数据塞到堆上
// 递归类型大小未知,必须用 Box 包一层,否则编译器说"无限大小"
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
let b = Box::new(5); // 5 在堆上,b 在栈上存指针
println!("{}", b); // *b 解引用,自动 deref coercion
let list = Cons(1, Box::new(Cons(2, Box::new(Nil))));
println!("链表建好了");
}
什么时候用 Box:递归类型、大小编译期未知的类型、想转移大数据所有权而不拷贝、想存 trait 对象(Box<dyn Trait>)。
Rc<T> 与 Arc<T>:多所有者共享
use std::rc::Rc;
fn main() {
// Rc:引用计数,单线程多个所有者共享同一份数据
let a = Rc::new(String::from("共享"));
let b = Rc::clone(&a); // 只增加引用计数,不拷贝数据
println!("强引用数 {}", Rc::strong_count(&a)); // 2
println!("{} 与 {}", a, b);
// 多线程场景把 Rc 换成 Arc(原子引用计数,线程安全)
}
$ cargo run
强引用数 2
共享 与 共享
| Rc<T> | Arc<T> | |
|---|---|---|
| 线程安全 | 不安全(不是 Send) | 安全(原子计数) |
| 开销 | 略小 | 略大(原子操作) |
| 用途 | 单线程多所有者 | 跨线程多所有者,std::sync::Arc |
RefCell<T>:编译期借不过,运行时借
论内部可变性模式
正常情况借用规则编译期查。但有些场景你"逻辑上"确定没冲突,编译期却查不出来——这时候用 RefCell<T>:借用检查推迟到运行时。它 .borrow() 读、.borrow_mut() 写,真冲突了运行时 panic(不编译错)。单线程用 RefCell;Cell<T> 给 Copy 类型用 get/set。
use std::cell::RefCell;
fn main() {
let cell = RefCell::new(5);
*cell.borrow_mut() = 10; // 运行时可变借用
println!("{}", cell.borrow()); // 运行时不可变借用
}
$ cargo run
10
黄金组合:多所有者可变共享
| 需求 | 用这个组合 |
|---|---|
| 单线程,多人共有且可变 | Rc<RefCell<T>> |
| 多线程,多人共有且可变 | Arc<Mutex<T>>(下一章) |
| 只想要共享不可变 | Rc<T> / Arc<T> |
循环引用与 Weak
Rc 互相引用会形成环,计数永远归不了零,内存泄漏。解法是用 Weak<T>(弱引用,不增加计数,.upgrade() 返回 Option<Rc<T>>)。常见在"父节点持有子节点强引用,子节点持有父节点弱引用"的树结构里。
另外两个底层:Cow<'a, B> 是写时复制——读零成本,真要写时才克隆,适合"大多只读偶尔改写";Deref 和 Drop 两个 trait 分别管 * 解引用和析构,是自定义智能指针的基础。
Cell<T>:Copy 类型的轻量内部可变性
use std::cell::Cell;
fn main() {
// Cell 适合 i32/bool 这种 Copy 类型:不拿引用,直接整体替换
let c = Cell::new(5);
c.set(10); // 即使 c 是不可变绑定,也能改内部值
println!("{}", c.get()); // 10,整体拷出来
}
$ cargo run
10
| Cell<T> | RefCell<T> | |
|---|---|---|
| 适用类型 | 只适合 T: Copy(i32/bool 等) | 任意 T |
| 改值方式 | .set() 整体替换,不发引用 | .borrow_mut() 拿可变引用 |
| 运行时开销 | 更小(不做借用计数) | 有借用计数,写错 panic |
| 典型用途 | 计数器、缓存标志位 | 需要持有复杂类型的可变引用 |