先搞清楚一件事
Rust 的"智能指针"概念和 C++ 完全是两码事。在 C++ 里,智能指针是用来擦屁股的——程序员管不好内存,标准库帮你想办法。Rust 不一样,所有权系统在编译期就解决了 99% 的内存安全问题,所谓的智能指针只是所有权规则的延伸。
记住这句话:Rust 的智能指针不是为了修复烂代码,而是为了实现特定的数据结构需求。
Box:最简单的堆分配
Box<T> 就是 Rust 版的 std::unique_ptr,但更简单、更严格。
1 2 3 4 5 6 7
| fn main() { let b = Box::new(5); println!("{}", b);
}
|
什么时候用 Box?
- 递归类型必须用它(这是编译器逼你的):
1 2 3 4
| struct ListNode { val: i32, next: Option<Box<ListNode>> }
|
- 数据太大,不想栈上拷贝:
1 2 3
| fn process(data: Box<[u8; 1024 * 1024]>) { }
|
- Trait Object:
1 2 3
| fn factory() -> Box<dyn Draw> { Box::new(Circle::new()) }
|
Rc:单线程共享所有权
Rc<T>(Reference Counted)是单线程的共享指针。注意:它只适用于单线程。
1 2 3 4 5 6 7 8 9 10 11 12 13
| use std::rc::Rc;
fn main() { let data = Rc::new(vec![1, 2, 3]); let data2 = Rc::clone(&data); let data3 = Rc::clone(&data); println!("引用计数: {}", Rc::strong_count(&data)); }
|
核心特点:
- 不可变借用:你只能读,不能写
- 非线程安全:没有
Send 和 Sync trait - 有循环引用风险(需要用
Weak<T> 破解)
1 2 3 4 5 6 7 8
| use std::rc::{Rc, Weak}; use std::cell::RefCell;
struct Node { value: i32, parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>>, }
|
Arc:线程安全的共享
Arc<T>(Atomic Reference Counted)是线程安全版的 Rc<T>。内部用原子操作维护引用计数,有性能开销。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| use std::sync::Arc; use std::thread;
fn main() { let data = Arc::new(vec![1, 2, 3]); let handles: Vec<_> = (0..10) .map(|i| { let data = Arc::clone(&data); thread::spawn(move || { println!("线程 {}: {:?}", i, data); }) }) .collect(); for h in handles { h.join().unwrap(); } }
|
Arc 的坑:
Arc 给你的是共享所有权,但如果你需要修改数据,还得配合 Mutex<T> 或 RwLock<T>:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| use std::sync::{Arc, Mutex};
fn main() { let counter = Arc::new(Mutex::new(0)); let handles: Vec<_> = (0..10) .map(|_| { let counter = Arc::clone(&counter); thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }) }) .collect(); for h in handles { h.join().unwrap(); } println!("结果: {}", *counter.lock().unwrap()); }
|
RefCell:运行期借用检查
RefCell<T> 实现了内部可变性(Interior Mutability)——让你能在不可变引用里修改数据。
原理:
- 普通借用检查在编译期进行
- RefCell 把检查推迟到运行期
- 违反规则会panic,而不是编译错误
1 2 3 4 5 6 7 8 9 10 11 12 13
| use std::cell::RefCell;
fn main() { let cell = RefCell::new(5); { let mut v = cell.borrow_mut(); *v += 1; } let v = cell.borrow(); println!("{}", v); }
|
双借用错误(运行期):
1 2 3
| let cell = RefCell::new(5); let _a = cell.borrow_mut(); let _b = cell.borrow_mut();
|
Rc<RefCell> 组合:
这是单线程下共享可变数据的经典套路:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| use std::rc::Rc; use std::cell::RefCell;
fn main() { let shared = Rc::new(RefCell::new(Vec::new())); let s1 = Rc::clone(&shared); let s2 = Rc::clone(&shared); s1.borrow_mut().push(1); s2.borrow_mut().push(2); println!("{:?}", shared.borrow()); }
|
Cell:更轻量的内部可变性
Cell<T> 也是内部可变性,但比 RefCell 更轻量。它只适用于实现了 Copy trait 的类型。
1 2 3 4 5 6 7 8 9 10
| use std::cell::Cell;
fn main() { let cell = Cell::new(5); cell.set(10); let old = cell.replace(20); println!("old: {}, new: {}", old, cell.get()); }
|
Cell vs RefCell:
| 特性 | Cell | RefCell |
|---|
| 适用类型 | Copy 类型 | 任意类型 |
| 借用检查 | 无(整体替换) | 运行期检查 |
| 开销 | 极低 | 有一定开销 |
| 获取引用 | 不能 | borrow() / borrow_mut() |
与 C++ 智能指针的对比
| 特性 | Rust | C++ |
|---|
| 所有权检查 | 编译期强制 | 运行期/程序员自律 |
| 默认行为 | 移动语义 | 拷贝语义 |
Box<T> / unique_ptr | 编译期 guarantee | 运行期可能 nullptr |
Rc<T> / shared_ptr | 明确单线程限制 | 一律线程安全(有开销) |
| 循环引用 | Weak 明确标记 | 程序员自己注意 |
| 性能 | 零成本抽象 | 虚函数/控制块开销 |
Linus 的评价:
"Rust 这帮家伙做对了一件事——让编译器当坏人。
C++ 的智能指针是亡羊补牢,Rust 的所有权系统是防患于未然。
我讨厌 C++ 的复杂性,但 Rust 的编译错误虽然烦人,至少问题在编译期就暴露了。"
选择指南:实际场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| struct TreeNode { value: i32, children: Vec<Rc<RefCell<TreeNode>>>, parent: Option<Weak<RefCell<TreeNode>>>, }
lazy_static! { static ref CONFIG: Arc<RwLock<Config>> = Arc::new(RwLock::new(Config::default())); }
let data = Rc::new(RefCell::new(Vec::new())); let closure = { let data = Rc::clone(&data); move || { data.borrow_mut().push(42); } };
fn process_big_data(data: Box<[u8; 1024 * 1024]>) { }
|
常见的混合使用
实际项目中,智能指针经常组合使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let state = Rc::new(RefCell::new(GameState::new())); state.borrow_mut().score += 100;
let counter = Arc::new(Mutex::new(0)); *counter.lock().unwrap() += 1;
let config = Arc::new(RwLock::new(Config::default())); let cfg = config.read().unwrap(); let mut cfg = config.write().unwrap();
let count = Rc::new(Cell::new(0)); count.set(count.get() + 1);
let callbacks: Rc<RefCell<Vec<Box<dyn Fn()>>>> = Rc::new(RefCell::new(Vec::new())); callbacks.borrow_mut().push(Box::new(|| println!("callback")));
|
选择建议:
| 场景 | 组合 | 注意点 |
|---|
| 单线程 + 共享 + 可变 | Rc<RefCell<T>> | 运行期检查,可能 panic |
| 单线程 + Copy类型可变 | Rc<Cell<T>> | 无借用检查,更轻量 |
| 多线程 + 共享 + 可变 | Arc<Mutex<T>> | 锁粒度要小,避免死锁 |
| 多读少写 | Arc<RwLock<T>> | 注意写锁饥饿问题 |
| 递归数据结构 | Box + Rc + Weak | 用 Weak 打破循环引用 |
关键原则
- 默认用 ownership + borrowing,别一上来就用智能指针
- Box:递归类型、大对象、Trait Object
- Rc:单线程共享只读数据(配合 RefCell 可写)
- Arc:多线程共享只读数据(配合 Mutex/RwLock 可写)
- Cell:Copy 类型的内部可变性
- RefCell:非 Copy 类型的内部可变性(单线程)
记住: Rust 的智能指针是为了表达特定的所有权语义,不是为了修复你的设计缺陷。如果你在纠结用哪个指针,先想想你的所有权模型设计是否正确。
2. Box:递归类型、大对象、Trait Object
3. Rc:单线程共享只读数据(配合 RefCell 可写)
4. Arc:多线程共享只读数据(配合 Mutex/RwLock 可写)
5. Cell:Copy 类型的内部可变性
6. RefCell:非 Copy 类型的内部可变性(单线程)