9.2 Rc<T> 与 Arc<T>
听
Rust 的所有权模型默认遵循“单一所有者”原则,即一个值在同一时间只能被一个变量拥有。然而,在某些场景下,多个部分需要共享同一份数据,且无法在编译期确定谁最后使用它。为此,Rust 提供了基于引用计数(Reference Counting)的智能指针:Rc<T>(单线程)和 Arc<T>(多线程)。
Rc<T>:单线程共享所有权
Rc<T>(“Reference Counted”)允许多个所有者共享堆上同一数据。每当创建一个新的 Rc 指向该数据时,引用计数加一;当某个 Rc 离开作用域时,计数减一;当计数归零时,数据被自动释放。
基本用法:
use std::rc::Rc;
let data = Rc::new(42);
let a = Rc::clone(&data);
let b = Rc::clone(&data);
println!("count after clones: {}", Rc::strong_count(&data)); // 3
drop(a);
println!("count after dropping a: {}", Rc::strong_count(&data)); // 2
注意:应使用 Rc::clone() 而非直接 clone(),虽然两者等价,但前者更明确表达“增加引用计数”而非“深拷贝”。
共享不可变数据
Rc<T> 只提供不可变引用(&T),因此不能通过它修改内部数据:
let list = Rc::new(vec![1, 2, 3]);
let list2 = Rc::clone(&list);
// list.push(4); // ❌ 无法获取可变引用
若需在共享的同时修改数据,需结合 RefCell<T>(见 9.3 节)。
典型应用场景
- 图结构中多个节点引用同一子图;
- 函数式编程中的共享不可变状态;
- 避免大对象的重复克隆。
Arc<T>:线程安全的引用计数
Rc<T> 不是线程安全的(未实现 Send 和 Sync),不能跨线程传递。在并发环境中,应使用 Arc<T>(“Atomically Reference Counted”)。
Arc<T> 与 Rc<T> 接口几乎相同,但内部使用原子操作更新引用计数,确保多线程下的内存安全:
use std::sync::Arc;
use std::thread;
let data = Arc::new(vec![1, 2, 3]);
let mut handles = vec![];
for _ in 0..3 {
let d = Arc::clone(&data);
let handle = thread::spawn(move || {
println!("Thread got: {:?}", d);
});
handles.push(handle);
}
for h in handles {
h.join().unwrap();
}
这里,多个线程共享同一个 Vec,而 Arc 确保其在所有线程结束后才被释放。
性能与限制
- 开销:每次克隆或丢弃
Rc/Arc都涉及计数更新;Arc的原子操作比Rc略慢; - 不可变性:两者均不提供内部可变性,需配合
RefCell(单线程)或Mutex(多线程); - 循环引用风险:若两个
Rc相互持有对方,会导致内存泄漏(引用计数永不归零)。可通过Weak<T>打破循环(标准库提供,但本章不展开)。
小结
Rc<T> 和 Arc<T> 扩展了 Rust 的所有权模型,支持多所有者共享数据。前者用于单线程高效共享,后者用于安全的跨线程共享。它们本身不提供可变性,但与 RefCell 或 Mutex 组合后,可构建灵活且安全的共享状态模型。合理使用引用计数,是在保持内存安全的前提下处理复杂数据依赖的关键手段。
#Rust 入门教程
分享于 1 周前
上一篇:9.1 Box<T>:堆分配
下一篇:9.3 RefCell<T> 与内部可变性