当我们使用 RefCell<Rc<T>>
或者类似的类型嵌套组合(具备内部可变性和引用计数)时,特别注意由于循环引用导致的堆栈溢出(main线程8M),造出内存泄漏。如:
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("a的初始化rc计数 = {}", Rc::strong_count(&a));
println!("a指向的节点 = {:?}", a.tail());
// 创建`b`到`a`的引用
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("在b创建后,a的rc计数 = {}", Rc::strong_count(&a));
println!("b的初始化rc计数 = {}", Rc::strong_count(&b));
println!("b指向的节点 = {:?}", b.tail());
// 利用RefCell的可变性,创建了`a`到`b`的引用
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}
println!("在更改a后,b的rc计数 = {}", Rc::strong_count(&b));
println!("在更改a后,a的rc计数 = {}", Rc::strong_count(&a));
// 下面一行println!将导致循环引用
// 我们可怜的8MB大小的main线程栈空间将被它冲垮,最终造成栈溢出
// println!("a next item = {:?}", a.tail());
}
运行结果:
a的初始化rc计数 = 1
a指向的节点 = Some(RefCell { value: Nil })
在b创建后,a的rc计数 = 2
b的初始化rc计数 = 1
b指向的节点 = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
在更改a后,b的rc计数 = 2
在更改a后,a的rc计数 = 2
这里我们创建了一个复杂的枚举类型 List,
它的每个值都指向了另一个 List,
由此此我们成功创建了循环引用a
-> b
-> a
-> b
····,有上述代码我们可以看出:在创建了 a
后,紧接着就使用 a
创建了 b
,因此 b
引用了 a,
然后又利用 Rc
克隆了 b
,通过 RefCell
的可变性让 a
引用了 b
在 main
函数结束前,a
和 b
的引用计数均是 2
,随后 b
触发 Drop
,此时引用计数会变为 1
,并不会归 0
,因此 b
所指向内存不会被释放,同理可得 a
指向的内存也不会被释放,最终发生了内存泄漏。由图来展示这种引用循环关系:
最后的注释代码通过a.tail
的调用,Rust 试图打印出 a -> b -> a ···
的所有内容,最终main线程操作8M,发生栈溢出。对于这类问题,我们可以使用Weak来解决。
Weak
非常类似于 Rc
,但Weak
不持有所有权,它仅仅保存一份指向数据的弱引用,通过 Weak
指针的upgrade
方法可以实现数据的访问,该方法返回一个类型为 Option<Rc<T>>
的值,Weak
本身不对值的存在性做任何担保,引用的值还存在就返回 Some
,不存在就返回 None
。对于父子引用关系,可以让父节点通过 Rc
来引用子节点,然后让子节点通过 Weak
来引用父节点。
Weak
通过 use std::rc::Weak
来引入,它具有以下特点:
- 可访问,但没有所有权,不增加引用计数,因此不会影响被引用值的释放回收
- 可由
Rc<T>
调用downgrade
方法转换成Weak<T>
Weak<T>
可使用upgrade
方法转换成Option<Rc<T>>
,如果资源已经被释放,则Option
的值是None
- 常用于解决循环引用的问题
一个使用Weak解决循环引用问题的例子
use std::rc::Rc;
use std::rc::Weak;
use std::cell::RefCell;
// 主人
struct Owner {
name: String,
gadgets: RefCell<Vec<Weak<Gadget>>>,
}
// 工具
struct Gadget {
id: i32,
owner: Rc<Owner>,
}
fn main() {
// 创建一个 Owner
// 需要注意,该 Owner 也拥有多个 `gadgets`
let gadget_owner : Rc<Owner> = Rc::new(
Owner {
name: "Gadget Man".to_string(),
gadgets: RefCell::new(Vec::new()),
}
);
// 创建工具,同时与主人进行关联:创建两个 gadget,他们分别持有 gadget_owner 的一个引用。
let gadget1 = Rc::new(Gadget{id: 1, owner: gadget_owner.clone()});
let gadget2 = Rc::new(Gadget{id: 2, owner: gadget_owner.clone()});
// 为主人更新它所拥有的工具
// 因为之前使用了 `Rc`,现在必须要使用 `Weak`,否则就会循环引用
gadget_owner.gadgets.borrow_mut().push(Rc::downgrade(&gadget1));
gadget_owner.gadgets.borrow_mut().push(Rc::downgrade(&gadget2));
// 遍历 gadget_owner 的 gadgets 字段
for gadget_opt in gadget_owner.gadgets.borrow().iter() {
// gadget_opt 是一个 Weak<Gadget> 。 因为 weak 指针不能保证他所引用的对象
// 仍然存在。所以我们需要显式的调用 upgrade() 来通过其返回值(Option<_>)来判
// 断其所指向的对象是否存在。
// 当然,Option 为 None 的时候这个引用原对象就不存在了。
let gadget = gadget_opt.upgrade().unwrap();
println!("Gadget {} owned by {}", gadget.id, gadget.owner.name);
}
// 在 main 函数的最后,gadget_owner,gadget1 和 gadget2 都被销毁。
// 具体是,因为这几个结构体之间没有了强引用(`Rc<T>`),所以,当他们销毁的时候。
// 首先 gadget2 和 gadget1 被销毁。
// 然后因为 gadget_owner 的引用数量为 0,所以这个对象可以被销毁了。
// 循环引用问题也就避免了
}
结构体中的自引用
自引用最麻烦的就是创建引用的同时,值的所有权会被转移,例如:
struct SelfRef<'a> {
value: String,
// 该引用指向上面的value
pointer_to_value: &'a str,
}
fn main(){
let s = "aaa".to_string();
let v = SelfRef {
value: s, //所有权转移
pointer_to_value: &s //借用
};
}
因为我们试图同时使用值和值的引用,最终所有权转移和借用一起发生了。其中一种解决方案是使用unsafe实现:
#[derive(Debug)]
struct SelfRef {
value: String,
pointer_to_value: *mut String,
}
impl SelfRef {
fn new(txt: &str) -> Self {
SelfRef {
value: String::from(txt),
pointer_to_value: std::ptr::null_mut(),
}
}
fn init(&mut self) {
let self_ref: *mut String = &mut self.value;
self.pointer_to_value = self_ref;
}
fn value(&self) -> &str {
&self.value
}
fn pointer_to_value(&self) -> &String {
assert!(!self.pointer_to_value.is_null(), "Test::b called without Test::init being called first");
unsafe { &*(self.pointer_to_value) }
}
}
fn main() {
let mut t = SelfRef::new("hello");
t.init();
println!("{}, {:p}", t.value(), t.pointer_to_value());
t.value.push_str(", world");
unsafe {
(&mut *t.pointer_to_value).push_str("!");
}
println!("{}, {:p}", t.value(), t.pointer_to_value());
}
编译输出:
hello, 0x16f3aec70
hello, world!, 0x16f3aec70
我们在 pointer_to_value
中直接存储裸指针,而不是 Rust 的引用,因此不再受到 Rust 借用规则和生命周期的限制,而且实现起来非常清晰、简洁。但是缺点就是,通过指针获取值时需要使用 unsafe
代码。