Rust 程序设计语言第 15.4 节《Rc<T>引用计数智能指针》的核心内容总结如下:
1. 为什么需要Rc<T>?
- 多所有权场景:在 Rust 严格的所有权规则下,一个值通常只有一个所有者。但在某些数据结构(如图、树中多个分支共享子节点)中,单个值可能需要有多个所有者。
- 编译时未知生命周期:当需要在堆上分配数据供程序的多个部分读取,且无法在编译时确定哪一部分会最后结束使用该数据时,普通的
Box<T>或引用无法满足需求(会导致所有权被移动或生命周期标注过于复杂)。 - 解决方案:
Rc<T>是Reference Counted(引用计数)的缩写。它通过在底层维护一个指向堆上数据的引用计数,来跟踪当前有多少个所有者正在使用该数据。只要引用计数大于 0,数据就一直有效;当引用计数归零时,数据会被自动清理。
2. 使用限制
- 仅限单线程:
Rc<T>只能用于单线程场景。如果用于多线程,会导致引用计数不安全(它没有实现线程安全的Send和Sync)。多线程环境下的引用计数需要使用Arc<T>。 - 只读共享:
Rc<T>提供的只能是不可变引用(只读数据)。它不允许通过多个可变引用修改数据,以防止违反 Rust 的借用规则(避免数据竞争)。若需在共享的同时修改数据,通常需要配合RefCell<T>(内部可变性模式)。
3. 代码示例对比
Box<T>的局限:
若尝试让两个链表b和c共享同一个子列表a(使用Box<T>),当b包含a时,a的所有权会被移动(move),导致无法再将其赋给c,从而引发编译错误。- 使用
Rc<T>解决:
将List定义中的Box<List>替换为Rc<List>。创建b和c时,通过克隆Rc指针来共享所有权:
usestd::rc::Rc;enumList{Cons(i32,Rc<List>),Nil,}fnmain(){leta=Rc::new(List::Cons(5,Rc::new(List::Cons(10,Rc::new(List::Nil)))));// 使用 Rc::clone(&a) 增加引用计数,而不是移动所有权letb=List::Cons(3,Rc::clone(&a));letc=List::Cons(4,Rc::clone(&a));}4. 关键特性与惯用法
**
Rc::clone(&a)vsa.clone()**:两者效果相同,但 Rust 的惯例是使用
Rc::clone(&a)。Rc::clone的实现不会对底层数据进行深拷贝(Deep Copy),它仅仅是将引用计数加 1,操作非常快。这种写法能在视觉上与“深拷贝式的 clone”区分开,方便在排查性能问题时迅速定位到耗时的深拷贝操作。
检查引用计数 (
Rc::strong_count):可以通过
Rc::strong_count(&a)查看当前的强引用计数。当作用域结束或变量被丢弃时,
Droptrait 会自动将引用计数减 1,无需手动调用减少计数的函数。