Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

English Original

智能指针:当单一所有权不够用时

你将学到什么: Box<T>Rc<T>Arc<T>RefCell<T>Cow<'a, T> 的使用场景,它们与 C# 中由 GC 管理的引用有什么区别,Drop 如何对应 Rust 版的 IDisposable,以及如何通过决策树选出合适的智能指针。

难度: 高级

在 C# 里,几乎所有对象都处在 GC 管理下,可以被多个引用指向。而在 Rust 中,默认模型是单一所有权;但有些时候你确实需要共享所有权、显式堆分配,或者内部可变性。这就是智能指针登场的地方。


常见的智能指针

1. Box<T> (堆分配)

最基础的智能指针。当你需要把一个值存放在堆(Heap)上而不是栈(Stack)上时,就使用它。

  • 使用场景: 递归数据结构(编译器无法在编译期确定其大小)。
#![allow(unused)]
fn main() {
enum List {
    Cons(i32, Box<List>),
    Nil,
}
}

2. Rc<T> (引用计数)

允许在单线程环境中,针对同一个数据拥有多个所有者

  • 使用场景: 图(Graph)节点或共享的配置对象。
#![allow(unused)]
fn main() {
let shared = Rc::new(vec![1, 2, 3]);
let a = Rc::clone(&shared);
let b = Rc::clone(&shared); // 引用计数现在为 3
}

3. Arc<T> (原子引用计数)

Rc<T> 的线程安全版本。

  • 使用场景: 在多个线程之间共享数据。
#![allow(unused)]
fn main() {
let shared_data = Arc::new(vec![10, 20]);
thread::spawn(move || {
    println!("{:?}", shared_data);
});
}

4. RefCell<T> (内部可变性)

即使你只持有指向容器的不可变引用 (&T),它也允许你修改内部数据。它将借用检查从编译期移到了运行期

  • 使用场景: Mock 对象,或者那些用单一可变性很难表达的复杂状态。

Drop:Rust 版本的 IDisposable

在 C# 中,你使用 usingIDisposable 来清理资源。而在 Rust 中,你需要实现 Drop trait。关键区别在于:Drop自动触发的。

#![allow(unused)]
fn main() {
struct TempFile { path: String }

impl Drop for TempFile {
    fn drop(&mut self) {
        println!("正在删除 {}", self.path);
        // 当 'temp' 离开作用域时,清理工作一定会被执行。
    }
}
}

关键理解: 在 Rust 中你永远不会“忘记”释放资源。只要所有者消失了,资源就会被清理掉。


决策树:该选哪种智能指针?

需求智能指针
堆分配 (单一所有权)Box<T>
共享所有权 (单线程)Rc<T>
共享所有权 (多线程)Arc<T>
通过 &T 修改内部 (单线程)RefCell<T>
通过 &T 修改内部 (多线程)Mutex<T>RwLock<T>

练习:为场景选择智能指针

挑战: 针对一个可能被多个线程同时访问的共享配置对象,你会选择哪种指针?

答案: Arc<T>。如果该配置还需要动态更新,则应使用 Arc<Mutex<T>>要点: 始终从最简单的拥有所有权的普通类型开始。只有在编译器给出提示(或者架构确实需要)时,再逐步升级到智能指针。