定义
如果两个或更多的 Rc
(引用计数智能指针)实例互相持有对方或者通过其他对象间接组成一个循环引用,会导致内存泄漏。这是因为 Rc
通过引用计数来管理其底层数据的生命周期,而循环引用导致引用计数永远不会降为零,因此内存无法被释放
循环引用
#[derive(Debug)]
struct Node {
value: i32,
next: Option<Rc<RefCell<Node>>>,
}
use std::cell::RefCell;
fn main() {
let first = Rc::new(RefCell::new(Node {
value: 1,
next: None,
}));
let second = Rc::new(RefCell::new(Node {
value: 2,
next: Some(Rc::clone(&first)),
}));
{
let mut first_borrowed = first.borrow_mut();
first_borrowed.next = Some(Rc::clone(&second)); // 形成引用循环
}
// 在这个点上,两个 Rc 的引用计数都不可能为 0,内存将永远不会释放
println!("first count: {}", Rc::strong_count(&first)); // 2
println!("second count: {}", Rc::strong_count(&second)); // 2
println!("这里打印会无限循环,最终报错: fatal runtime error: stack overflow {:?}", first)
}
解决循环引用
一种常见解决方案是使用 Rc
和 Weak
智能指针的组合。Weak
是 Rc
的弱引用版本,它不增加引用计数,从而打破循环引用
#[derive(Debug)]
struct Node {
value: i32,
next: Option<Rc<RefCell<Node>>>,
prev: Option<Weak<RefCell<Node>>>, // 使用 Weak 引用打破循环
}
use std::rc::{Rc, Weak};
use std::cell::RefCell;
fn main() {
// 创建第一个节点
let first = Rc::new(RefCell::new(Node {
value: 1,
next: None,
prev: None,
}));
// 创建第二个节点,并让它的 prev 指向第一个节点
let second = Rc::new(RefCell::new(Node {
value: 2,
next: None,
prev: Some(Rc::downgrade(&first)), // 使用 Weak 指针 [1 <- 2]
}));
// 更新第一个节点的 next 指向第二个节点 [1 -> 2]
first.borrow_mut().next = Some(Rc::clone(&second));
// 打印引用计数
println!("first strong count: {}", Rc::strong_count(&first)); // 打印1: 只有自己
println!("second strong count: {}", Rc::strong_count(&second)); // 打印2:自己的引用 + 1.next 指向自己的引用
// 打印第二个节点的 prev 是否存在
{
let second_prev = first.borrow().next.as_ref().unwrap().borrow().prev.as_ref().unwrap().upgrade();
println!("Second node's previous exists: {:?}", second_prev.is_some());
}
// 程序结束时,引用会被正确释放
}
关键说明:
Rc::downgrade
: 获取指向first
的Weak
引用,而不是Rc
引用。Weak
引用不会增加引用计数,这样即使second.prev
持有first
的相应引用,也不会导致循环引用Weak::upgrade
: 在需要使用时,将Weak
引用升级为Option<T>
。如果Rc
仍然存在,则upgrade
会返回Some
,否则返回None