trpl-zh-cn/src/ch15-06-reference-cycles.md
2017-03-17 00:00:53 +08:00

3.7 KiB
Raw Blame History

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

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