Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

引用循环可能导致内存泄漏

Reference Cycles Can Leak Memory

Rust 的内存安全保证使得无意中创建永远无法清理的内存(被称为 内存泄漏,memory leak)变得很困难,但并非不可能。完全防止内存泄漏并不是 Rust 的保证之一,这意味着内存泄漏在 Rust 中是内存安全的。我们可以看到,通过使用 Rc<T>RefCell<T>,Rust 允许内存泄漏:可能会创建项与项之间循环引用的引用。这会导致内存泄漏,因为循环中每个项的引用计数永远不会达到 0,值也永远不会被丢弃。

Rust’s memory safety guarantees make it difficult, but not impossible, to accidentally create memory that is never cleaned up (known as a memory leak). Preventing memory leaks entirely is not one of Rust’s guarantees, meaning memory leaks are memory safe in Rust. We can see that Rust allows memory leaks by using Rc<T> and RefCell<T>: It’s possible to create references where items refer to each other in a cycle. This creates memory leaks because the reference count of each item in the cycle will never reach 0, and the values will never be dropped.

创建引用循环

Creating a Reference Cycle

让我们看看引用循环是如何发生的以及如何防止它。我们从示例 15-25 中 List 枚举的定义和 tail 方法开始。

Let’s look at how a reference cycle might happen and how to prevent it, starting with the definition of the List enum and a tail method in Listing 15-25.

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() {}

我们使用了示例 15-5 中 List 定义的另一个变体。Cons 变体中的第二个元素现在是 RefCell<Rc<List>>,这意味着我们不再像示例 15-24 那样拥有修改 i32 值的能力,而是想要修改 Cons 变体指向的 List 值。我们还添加了一个 tail 方法,以便在拥有 Cons 变体时方便地访问第二个项。

We’re using another variation of the List definition from Listing 15-5. The second element in the Cons variant is now RefCell<Rc<List>>, meaning that instead of having the ability to modify the i32 value as we did in Listing 15-24, we want to modify the List value a Cons variant is pointing to. We’re also adding a tail method to make it convenient for us to access the second item if we have a Cons variant.

在示例 15-26 中,我们添加了一个使用示例 15-25 中定义的 main 函数。这段代码在 a 中创建了一个列表,在 b 中创建了一个指向 a 中列表的列表。然后,它修改 a 中的列表以指向 b,从而创建了一个引用循环。在此过程中有多个 println! 语句显示了各个阶段的引用计数。

In Listing 15-26, we’re adding a main function that uses the definitions in Listing 15-25. This code creates a list in a and a list in b that points to the list in a. Then, it modifies the list in a to point to b, creating a reference cycle. There are println! statements along the way to show what the reference counts are at various points in this process.

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 initial rc count = {}", Rc::strong_count(&a));
    println!("a next item = {:?}", a.tail());

    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));

    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(link) = a.tail() {
        *link.borrow_mut() = Rc::clone(&b);
    }

    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());
}

我们在变量 a 中创建了一个持有 List 值的 Rc<List> 实例,初始列表为 5, Nil。然后我们在变量 b 中创建了另一个持有 List 值的 Rc<List> 实例,它包含值 10 并指向 a 中的列表。

We create an Rc<List> instance holding a List value in the variable a with an initial list of 5, Nil. We then create an Rc<List> instance holding another List value in the variable b that contains the value 10 and points to the list in a.

我们修改 a 使其指向 b 而非 Nil,从而创建了一个循环。我们通过使用 tail 方法获取 aRefCell<Rc<List>> 的引用(存入变量 link)来实现这一点。接着,我们在 RefCell<Rc<List>> 上调用 borrow_mut 方法,将内部持有的 Nil 值的 Rc<List> 更改为指向 bRc<List>

We modify a so that it points to b instead of Nil, creating a cycle. We do that by using the tail method to get a reference to the RefCell<Rc<List>> in a, which we put in the variable link. Then, we use the borrow_mut method on the RefCell<Rc<List>> to change the value inside from an Rc<List> that holds a Nil value to the Rc<List> in b.

当我们运行这段代码(暂时保持最后一个 println! 为注释状态)时,将得到以下输出:

When we run this code, keeping the last println! commented out for the moment, we’ll get this output:

$ cargo run
   Compiling cons-list v0.1.0 (file:///projects/cons-list)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.53s
     Running `target/debug/cons-list`
a initial rc count = 1
a next item = Some(RefCell { value: Nil })
a rc count after b creation = 2
b initial rc count = 1
b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
b rc count after changing a = 2
a rc count after changing a = 2

在我们修改 a 使其指向 b 后,ab 中的 Rc<List> 实例的引用计数都是 2。在 main 结束时,Rust 丢弃变量 b,这将 bRc<List> 实例的引用计数从 2 减少到 1。Rc<List> 在堆上拥有的内存此时不会被丢弃,因为它的引用计数是 1,而不是 0。然后,Rust 丢弃 a,这同样将 aRc<List> 实例的引用计数从 2 减少到 1。这个实例的内存也无法被丢弃,因为另一个 Rc<List> 实例仍然引用它。分配给该列表的内存将永远无法被回收。为了直观展现这个引用循环,我们创建了图 15-4 所示的图表。

The reference count of the Rc<List> instances in both a and b is 2 after we change the list in a to point to b. At the end of main, Rust drops the variable b, which decreases the reference count of the b Rc<List> instance from 2 to 1. The memory that Rc<List> has on the heap won’t be dropped at this point because its reference count is 1, not 0. Then, Rust drops a, which decreases the reference count of the a Rc<List> instance from 2 to 1 as well. This instance’s memory can’t be dropped either, because the other Rc<List> instance still refers to it. The memory allocated to the list will remain uncollected forever. To visualize this reference cycle, we’ve created the diagram in Figure 15-4.

一个标记为 'a' 的矩形指向一个包含整数 5 的矩形。一个标记为 'b' 的矩形指向一个包含整数 10 的矩形。包含 5 的矩形指向包含 10 的矩形,而包含 10 的矩形又指回包含 5 的矩形,从而形成一个循环。 A rectangle labeled 'a' that points to a rectangle containing the integer 5. A rectangle labeled 'b' that points to a rectangle containing the integer 10. The rectangle containing 5 points to the rectangle containing 10, and the rectangle containing 10 points back to the rectangle containing 5, creating a cycle.

图 15-4:列表 ab 互相指向的引用循环 Figure 15-4: A reference cycle of lists a and b pointing to each other

如果你取消最后一个 println! 的注释并运行程序,Rust 将尝试打印这个循环:a 指向 b 指向 a 如此反复,直到栈溢出。

If you uncomment the last println! and run the program, Rust will try to print this cycle with a pointing to b pointing to a and so forth until it overflows the stack.

与现实世界的程序相比,在本例中创建引用循环的后果并不十分严重:创建引用循环后,程序就结束了。但是,如果一个更复杂的程序在一个循环中分配了大量内存并持有很长时间,程序将使用比它需要的更多的内存,并可能使系统不堪重负,导致可用内存耗尽。

Compared to a real-world program, the consequences of creating a reference cycle in this example aren’t very dire: Right after we create the reference cycle, the program ends. However, if a more complex program allocated lots of memory in a cycle and held onto it for a long time, the program would use more memory than it needed and might overwhelm the system, causing it to run out of available memory.

创建引用循环并不容易,但也并非不可能。如果你拥有的 RefCell<T> 值包含 Rc<T> 值或类似的具有内部可变性和引用计数的嵌套类型组合,你必须确保不创建循环;你不能指望 Rust 捕捉到它们。创建引用循环是你程序中的一个逻辑漏洞,你应该使用自动测试、代码审查和其他软件开发实践来将其降至最低。

Creating reference cycles is not easily done, but it’s not impossible either. If you have RefCell<T> values that contain Rc<T> values or similar nested combinations of types with interior mutability and reference counting, you must ensure that you don’t create cycles; you can’t rely on Rust to catch them. Creating a reference cycle would be a logic bug in your program that you should use automated tests, code reviews, and other software development practices to minimize.

避免引用循环的另一种解决方案是重构你的数据结构,使一些引用表达所有权,而另一些引用不表达所有权。结果是,你可以拥有由一些所有权关系和一些非所有权关系组成的循环,并且只有所有权关系会影响一个值是否可以被丢弃。在示例 15-25 中,我们总是希望 Cons 变体拥有它们的列表,因此重构数据结构是不可行的。让我们看一个使用由父节点和子节点组成的图的例子,看看非所有权关系何时是防止引用循环的合适方式。

Another solution for avoiding reference cycles is reorganizing your data structures so that some references express ownership and some references don’t. As a result, you can have cycles made up of some ownership relationships and some non-ownership relationships, and only the ownership relationships affect whether or not a value can be dropped. In Listing 15-25, we always want Cons variants to own their list, so reorganizing the data structure isn’t possible. Let’s look at an example using graphs made up of parent nodes and child nodes to see when non-ownership relationships are an appropriate way to prevent reference cycles.

使用 Weak<T> 防止引用循环

Preventing Reference Cycles Using Weak<T>

到目前为止,我们已经演示了调用 Rc::clone 会增加 Rc<T> 实例的 strong_count(强引用计数),并且只有当 strong_count 为 0 时 Rc<T> 实例才会被清理。你也可以通过调用 Rc::downgrade 并传递一个对 Rc<T> 的引用,来为 Rc<T> 实例中的值创建一个弱引用(weak reference)。强引用(Strong references)是你共享 Rc<T> 实例所有权的方式。弱引用(Weak references)不表达所有权关系,它们的计数不影响 Rc<T> 实例何时被清理。它们不会引起引用循环,因为任何涉及弱引用的循环一旦涉及值的强引用计数为 0 就会被打破。

So far, we’ve demonstrated that calling Rc::clone increases the strong_count of an Rc<T> instance, and an Rc<T> instance is only cleaned up if its strong_count is 0. You can also create a weak reference to the value within an Rc<T> instance by calling Rc::downgrade and passing a reference to the Rc<T>. Strong references are how you can share ownership of an Rc<T> instance. Weak references don’t express an ownership relationship, and their count doesn’t affect when an Rc<T> instance is cleaned up. They won’t cause a reference cycle, because any cycle involving some weak references will be broken once the strong reference count of values involved is 0.

当你调用 Rc::downgrade 时,你会得到一个 Weak<T> 类型的智能指针。调用 Rc::downgrade 会将 weak_count(弱引用计数)增加 1,而不是将 Rc<T> 实例中的 strong_count 增加 1。Rc<T> 类型使用 weak_count 来跟踪存在多少个 Weak<T> 引用,类似于 strong_count。不同之处在于,不需要 weak_count 为 0 就能清理 Rc<T> 实例。

When you call Rc::downgrade, you get a smart pointer of type Weak<T>. Instead of increasing the strong_count in the Rc<T> instance by 1, calling Rc::downgrade increases the weak_count by 1. The Rc<T> type uses weak_count to keep track of how many Weak<T> references exist, similar to strong_count. The difference is the weak_count doesn’t need to be 0 for the Rc<T> instance to be cleaned up.

因为 Weak<T> 引用的值可能已经被丢弃,所以要对 Weak<T> 指向的值执行任何操作,你必须确保该值仍然存在。通过在 Weak<T> 实例上调用 upgrade 方法来实现这一点,该方法将返回一个 Option<Rc<T>>。如果 Rc<T> 值尚未被丢弃,你将得到 Some 结果;如果 Rc<T> 值已被丢弃,你将得到 None 结果。因为 upgrade 返回 Option<Rc<T>>,Rust 会确保处理 Some 情况和 None 情况,并且不会出现无效指针。

Because the value that Weak<T> references might have been dropped, to do anything with the value that a Weak<T> is pointing to you must make sure the value still exists. Do this by calling the upgrade method on a Weak<T> instance, which will return an Option<Rc<T>>. You’ll get a result of Some if the Rc<T> value has not been dropped yet and a result of None if the Rc<T> value has been dropped. Because upgrade returns an Option<Rc<T>>, Rust will ensure that the Some case and the None case are handled, and there won’t be an invalid pointer.

举个例子,我们将创建一个树,其项目既知道其子项目 知道其父项目,而不是使用仅知道下一个项目的列表。

As an example, rather than using a list whose items know only about the next item, we’ll create a tree whose items know about their child items and their parent items.

创建树形数据结构

Creating a Tree Data Structure

首先,我们将构建一个具有其子节点信息的树。我们将创建一个名为 Node 的结构体,它持有其自身的 i32 值以及对其子 Node 值的引用:

To start, we’ll build a tree with nodes that know about their child nodes. We’ll create a struct named Node that holds its own i32 value as well as references to its child Node values:

文件名:src/main.rs Filename: src/main.rs

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Node {
    value: i32,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        children: RefCell::new(vec![]),
    });

    let branch = Rc::new(Node {
        value: 5,
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });
}

我们希望 Node 拥有其子节点,并希望与变量共享该所有权,以便我们可以直接访问树中的每个 Node。为此,我们将 Vec<T> 项定义为 Rc<Node> 类型的值。我们还想修改哪些节点是另一个节点的子节点,因此我们在 children 中的 Vec<Rc<Node>> 周围加了一个 RefCell<T>

We want a Node to own its children, and we want to share that ownership with variables so that we can access each Node in the tree directly. To do this, we define the Vec<T> items to be values of type Rc<Node>. We also want to modify which nodes are children of another node, so we have a RefCell<T> in children around the Vec<Rc<Node>>.

接下来,我们将使用我们的结构体定义,创建一个名为 leafNode 实例,值为 3 且没有子节点,以及另一个名为 branch 的实例,值为 5leaf 是其子节点之一,如示例 15-27 所示。

Next, we’ll use our struct definition and create one Node instance named leaf with the value 3 and no children, and another instance named branch with the value 5 and leaf as one of its children, as shown in Listing 15-27.

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Node {
    value: i32,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        children: RefCell::new(vec![]),
    });

    let branch = Rc::new(Node {
        value: 5,
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });
}

我们克隆 leaf 中的 Rc<Node> 并将其存储在 branch 中,这意味着 leaf 中的 Node 现在有两个所有者:leafbranch。我们可以通过 branch.childrenbranch 导航到 leaf,但无法从 leaf 导航到 branch。原因是 leaf 没有对 branch 的引用,并且不知道它们是相关的。我们希望 leaf 知道 branch 是它的父节点。我们接下来就这样做。

We clone the Rc<Node> in leaf and store that in branch, meaning the Node in leaf now has two owners: leaf and branch. We can get from branch to leaf through branch.children, but there’s no way to get from leaf to branch. The reason is that leaf has no reference to branch and doesn’t know they’re related. We want leaf to know that branch is its parent. We’ll do that next.

添加从子节点到父节点的引用

Adding a Reference from a Child to Its Parent

为了让子节点知道其父节点,我们需要向 Node 结构体定义添加一个 parent 字段。困难在于决定 parent 的类型。我们知道它不能包含 Rc<T>,因为那会创建一个引用循环,leaf.parent 指向 branchbranch.children 指向 leaf,这将导致它们的 strong_count 值永远不会为 0。

To make the child node aware of its parent, we need to add a parent field to our Node struct definition. The trouble is in deciding what the type of parent should be. We know it can’t contain an Rc<T>, because that would create a reference cycle with leaf.parent pointing to branch and branch.children pointing to leaf, which would cause their strong_count values to never be 0.

换种方式思考这些关系:父节点应该拥有其子节点:如果一个父节点被丢弃,它的子节点也应该被丢弃。然而,子节点不应该拥有其父节点:如果我们丢弃一个子节点,父节点应该仍然存在。这是弱引用的用例!

Thinking about the relationships another way, a parent node should own its children: If a parent node is dropped, its child nodes should be dropped as well. However, a child should not own its parent: If we drop a child node, the parent should still exist. This is a case for weak references!

因此,我们将 parent 的类型改为使用 Weak<T>,具体为 RefCell<Weak<Node>>,而不是 Rc<T>。现在我们的 Node 结构体定义如下所示:

So, instead of Rc<T>, we’ll make the type of parent use Weak<T>, specifically a RefCell<Weak<Node>>. Now our Node struct definition looks like this:

文件名:src/main.rs Filename: src/main.rs

use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    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!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

一个节点将能够引用其父节点,但并不拥有其父节点。在示例 15-28 中,我们更新 main 以使用这个新定义,这样 leaf 节点就有一种引用其父节点 branch 的方式。

A node will be able to refer to its parent node but doesn’t own its parent. In Listing 15-28, we update main to use this new definition so that the leaf node will have a way to refer to its parent, branch.

use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    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!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

创建 leaf 节点看起来与示例 15-27 类似,除了 parent 字段:leaf 开始时没有父节点,所以我们创建了一个新的空 Weak<Node> 引用实例。

Creating the leaf node looks similar to Listing 15-27 with the exception of the parent field: leaf starts out without a parent, so we create a new, empty Weak<Node> reference instance.

此时,当我们尝试使用 upgrade 方法获取 leaf 的父节点引用时,我们得到一个 None 值。我们在第一个 println! 语句的输出中看到了这一点:

At this point, when we try to get a reference to the parent of leaf by using the upgrade method, we get a None value. We see this in the output from the first println! statement:

leaf parent = None

当我们创建 branch 节点时,它的 parent 字段也将有一个新的 Weak<Node> 引用,因为 branch 也没有父节点。我们仍然将 leaf 作为 branch 的子节点之一。一旦我们在 branch 中有了 Node 实例,我们就可以修改 leaf 赋予它一个对其父节点的 Weak<Node> 引用。我们在 leafparent 字段中的 RefCell<Weak<Node>> 上使用 borrow_mut 方法,然后使用 Rc::downgrade 函数从 branch 中的 Rc<Node> 创建一个指向 branchWeak<Node> 引用。

When we create the branch node, it will also have a new Weak<Node> reference in the parent field because branch doesn’t have a parent node. We still have leaf as one of the children of branch. Once we have the Node instance in branch, we can modify leaf to give it a Weak<Node> reference to its parent. We use the borrow_mut method on the RefCell<Weak<Node>> in the parent field of leaf, and then we use the Rc::downgrade function to create a Weak<Node> reference to branch from the Rc<Node> in branch.

当我们再次打印 leaf 的父节点时,这一次我们将得到一个持有 branchSome 变体:现在 leaf 可以访问它的父节点了!当我们打印 leaf 时,我们也避免了像示例 15-26 那样最终导致栈溢出的循环;Weak<Node> 引用被打印为 (Weak)

When we print the parent of leaf again, this time we’ll get a Some variant holding branch: Now leaf can access its parent! When we print leaf, we also avoid the cycle that eventually ended in a stack overflow like we had in Listing 15-26; the Weak<Node> references are printed as (Weak):

leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },
children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },
children: RefCell { value: [] } }] } })

没有无限的输出表明这段代码没有创建引用循环。我们也可以通过查看调用 Rc::strong_countRc::weak_count 获得的值来得知这一点。

The lack of infinite output indicates that this code didn’t create a reference cycle. We can also tell this by looking at the values we get from calling Rc::strong_count and Rc::weak_count.

strong_countweak_count 变化的直观展现

Visualizing Changes to strong_count and weak_count

让我们看看通过创建一个新的内部作用域并将 branch 的创建移动到该作用域中,Rc<Node> 实例的 strong_countweak_count 值是如何变化的。通过这样做,我们可以看到当 branch 被创建以及随后因超出作用域而被丢弃时会发生什么。修改如示例 15-29 所示。

Let’s look at how the strong_count and weak_count values of the Rc<Node> instances change by creating a new inner scope and moving the creation of branch into that scope. By doing so, we can see what happens when branch is created and then dropped when it goes out of scope. The modifications are shown in Listing 15-29.

use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

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 = {}",
            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),
    );
}

leaf 被创建后,其 Rc<Node> 的强引用计数为 1,弱引用计数为 0。在内部作用域中,我们创建了 branch 并将其与 leaf 关联,此时当我们打印计数时,branch 中的 Rc<Node> 将具有 1 的强引用计数和 1 的弱引用计数(因为 leaf.parent 通过 Weak<Node> 指向 branch)。当我们打印 leaf 中的计数时,我们将看到它将具有 2 的强引用计数,因为 branch 现在在 branch.children 中存储了 leafRc<Node> 的克隆,但弱引用计数仍将为 0。

After leaf is created, its Rc<Node> has a strong count of 1 and a weak count of 0. In the inner scope, we create branch and associate it with leaf, at which point when we print the counts, the Rc<Node> in branch will have a strong count of 1 and a weak count of 1 (for leaf.parent pointing to branch with a Weak<Node>). When we print the counts in leaf, we’ll see it will have a strong count of 2 because branch now has a clone of the Rc<Node> of leaf stored in branch.children but will still have a weak count of 0.

当内部作用域结束时,branch 超出作用域,Rc<Node> 的强引用计数减少到 0,因此其 Node 被丢弃。来自 leaf.parent 的弱引用计数 1 对 Node 是否被丢弃没有影响,因此我们不会产生任何内存泄漏!

When the inner scope ends, branch goes out of scope and the strong count of the Rc<Node> decreases to 0, so its Node is dropped. The weak count of 1 from leaf.parent has no bearing on whether or not Node is dropped, so we don’t get any memory leaks!

如果我们在作用域结束后尝试访问 leaf 的父节点,我们将再次得到 None。在程序结束时,leaf 中的 Rc<Node> 具有 1 的强引用计数和 0 的弱引用计数,因为变量 leaf 现在又是对该 Rc<Node> 的唯一引用。

If we try to access the parent of leaf after the end of the scope, we’ll get None again. At the end of the program, the Rc<Node> in leaf has a strong count of 1 and a weak count of 0 because the variable leaf is now the only reference to the Rc<Node> again.

所有管理计数和值丢弃的逻辑都内置在 Rc<T>Weak<T> 及其 Drop trait 的实现中。通过在 Node 的定义中指定从子节点到父节点的关系应该是 Weak<T> 引用,你就能够让父节点指向子节点,反之亦然,而不会创建引用循环和内存泄漏。

All of the logic that manages the counts and value dropping is built into Rc<T> and Weak<T> and their implementations of the Drop trait. By specifying that the relationship from a child to its parent should be a Weak<T> reference in the definition of Node, you’re able to have parent nodes point to child nodes and vice versa without creating a reference cycle and memory leaks.

总结

Summary

本章介绍了如何使用智能指针来做出与 Rust 默认常规引用不同的保证和权衡。Box<T> 类型具有已知的大小,并指向在堆上分配的数据。Rc<T> 类型跟踪指向堆上数据的引用数量,以便数据可以拥有多个所有者。带有内部可变性的 RefCell<T> 类型让我们在需要不可变类型但需要更改其内部值时可以使用它;它还在运行时而不是编译时强制执行借用规则。

This chapter covered how to use smart pointers to make different guarantees and trade-offs from those Rust makes by default with regular references. The Box<T> type has a known size and points to data allocated on the heap. The Rc<T> type keeps track of the number of references to data on the heap so that the data can have multiple owners. The RefCell<T> type with its interior mutability gives us a type that we can use when we need an immutable type but need to change an inner value of that type; it also enforces the borrowing rules at runtime instead of at compile time.

此外还讨论了 DerefDrop trait,它们启用了智能指针的大部分功能。我们探索了可能导致内存泄漏的引用循环,以及如何使用 Weak<T> 防止它们。

Also discussed were the Deref and Drop traits, which enable a lot of the functionality of smart pointers. We explored reference cycles that can cause memory leaks and how to prevent them using Weak<T>.

如果本章引起了你的兴趣,并且你想要实现自己的智能指针,请查看 “The Rustonomicon” 以获取更多有用的信息。

If this chapter has piqued your interest and you want to implement your own smart pointers, check out “The Rustonomicon” for more useful information.

接下来,我们将讨论 Rust 中的并发。你甚至会了解到一些新的智能指针。

Next, we’ll talk about concurrency in Rust. You’ll even learn about a few new smart pointers.