引用循环和内存泄漏是安全的
ch15-06-reference-cycles.md
commit c49e5ee8859f8eb8f8867cbeafbdf5b802aa5894
我们讨论过 Rust 做出的一些保证,例如永远也不会遇到一个空值,而且数据竞争也会在编译时被阻止。Rust 的内存安全保证也使其更难以制造从不被清理的内存,这被称为内存泄露。然而 Rust 并不是不可能出现内存泄漏,避免内存泄露并不是 Rust 的保证之一。换句话说,内存泄露是安全的。
在使用Rc<T>
和RefCell<T>
时,有可能创建循环引用,这时各个项相互引用并形成环。这是不好的因为每一项的引用计数将永远也到不了 0,其值也永远也不会被丢弃。让我们看看这是如何发生的以及如何避免它。
在列表 15-16 中,我们将使用列表 15-5 中List
定义的另一个变体。我们将回到储存i32
值作为Cons
成员的第一个元素。现在Cons
成员的第二个元素是RefCell<Rc<List>>
:这时就不能修改i32
值了,但是能够修改Cons
成员指向的哪个List
。还需要增加一个tail
方法来方便我们在拥有一个Cons
成员时访问第二个项:
Filename: src/main.rs
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match *self {
Cons(_, ref item) => Some(item),
Nil => None,
}
}
}
接下来,在列表 15-17 中,我们将在变量a
中创建一个List
值,其内部是一个5, Nil
的列表。接着在变量b
创建一个值 10 和指向a
中列表的List
值。最后修改a
指向b
而不是Nil
,这会创建一个循环:
Filename: src/main.rs
# #[derive(Debug)]
# enum List {
# Cons(i32, RefCell<Rc<List>>),
# Nil,
# }
#
# impl List {
# fn tail(&self) -> Option<&RefCell<Rc<List>>> {
# match *self {
# Cons(_, ref item) => Some(item),
# Nil => None,
# }
# }
# }
#
use List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("a initial rc count = {}", Rc::strong_count(&a));
println!("a next item = {:?}", a.tail());
let b = Rc::new(Cons(10, RefCell::new(a.clone())));
println!("a rc count after b creation = {}", Rc::strong_count(&a));
println!("b initial rc count = {}", Rc::strong_count(&b));
println!("b next item = {:?}", b.tail());
if let Some(ref link) = a.tail() {
*link.borrow_mut() = b.clone();
}
println!("b rc count after changing a = {}", Rc::strong_count(&b));
println!("a rc count after changing a = {}", Rc::strong_count(&a));
// Uncomment the next line to see that we have a cycle; it will
// overflow the stack
// println!("a next item = {:?}", a.tail());
}
使用tail
方法来获取a
中RefCell
的引用,并将其放入变量link
中。接着对RefCell
使用borrow_mut
方法将其中的值从存放Nil
值的Rc
改为b
中的Rc
。这创建了一个看起来像图 15-18 所示的引用循环: