引用循环和内存泄漏是安全的

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,
        }
    }
}

Listing 15-16: A cons list definition that holds a RefCell so that we can modify what a Cons variant is referring to

接下来,在列表 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());
}

Listing 15-17: Creating a reference cycle of two List values pointing to each other

使用tail方法来获取aRefCell的引用,并将其放入变量link中。接着对RefCell使用borrow_mut方法将其中的值从存放Nil值的Rc改为b中的Rc。这创建了一个看起来像图 15-18 所示的引用循环:

Reference cycle of lists

Figure 15-18: A reference cycle of lists a and b pointing to each other