让我们通过创建了一个新的内部作用域并将 branch 的创建放入其中,来观察 Rc 实例的
strong_count 和 weak_count 值的变化。这会展示当 branch 创建和离开作用域被丢弃时会发生什么。
这些修改如示例 15-29 所示:
文件名: src∕main.rs
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak>,
children: RefCell<Vec<Rc>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
println!(
“leaf strong = {}, weak = {}”,
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
{
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
println!(
“branch strong = {}, weak = {}”,
Rc::strong_count(&branch),
Rc::weak_count(&branch),
);
println!(
“leaf strong = {}, weak = {}”,
15.6. 引用循环与内存泄漏 413
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
}
println!(“leaf parent = {:?}”, leaf.parent.borrow().upgrade());
println!(
“leaf strong = {}, weak = {}”,
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
}
示例 15-29:在内部作用域创建 branch 并检查其强弱引用计数
一旦创建了 leaf,其 Rc 的强引用计数为 1,弱引用计数为 0。在内部作用域中创建了 branch 并
与 leaf 相关联,此时 branch 中 Rc 的强引用计数为 1,弱引用计数为 1(因为 leaf.parent 通
过 Weak 指向 branch)。这里 leaf 的强引用计数为 2,因为现在 branch 的 branch.children 中
储存了 leaf 的 Rc 的拷贝,不过弱引用计数仍然为 0。
当内部作用域结束时,branch 离开作用域,Rc 的强引用计数减少为 0,所以其 Node 被丢弃。
来自 leaf.parent 的弱引用计数 1 与 Node 是否被丢弃无关,所以并没有产生任何内存泄漏!
如果在内部作用域结束后尝试访问 leaf 的父节点,会再次得到 None。在程序的结尾,leaf 中 Rc
的强引用计数为 1,弱引用计数为 0,因为现在 leaf 又是 Rc 唯一的引用了。
所有这些管理计数和值的逻辑都内建于 Rc 和 Weak 以及它们的 Drop trait 实现中。通过在 Node
定义中指定从子节点到父节点的关系为一个Weak引用,就能够拥有父节点和子节点之间的双向引用
而不会造成引用循环和内存泄漏。
总结
这一章涵盖了如何使用智能指针来做出不同于 Rust 常规引用默认所提供的保证与取舍。Box 有一
个已知的大小并指向分配在堆上的数据。Rc 记录了堆上数据的引用数量以便可以拥有多个所有者。
RefCell 和其内部可变性提供了一个可以用于当需要不可变类型但是需要改变其内部值能力的类型,
并在运行时而不是编译时检查借用规则。
我们还介绍了提供了很多智能指针功能的 trait Deref 和 Drop。同时探索了会造成内存泄漏的引用循环,
以及如何使用 Weak 来避免它们。
如果本章内容引起了你的兴趣并希望现在就实现你自己的智能指针的话,请阅读 ”The Rustonomicon”
来获取更多有用的信息。
接下来,让我们谈谈 Rust 的并发。届时甚至还会学习到一些新的对并发有帮助的智能指针。