x-i18n: generated_at: “2026-03-01T14:34:49Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: 3ef419d9f6d39ad665238bd0ead40f7ad65485e8762a8f8f64dd13d7152e5868 source_path: ch15-04-rc.md workflow: 16
Rc<T> 引用计数智能指针 (Rc<T>, the Reference-Counted Smart Pointer)
Rc<T>, the Reference-Counted Smart Pointer
在大多数情况下,所有权是明确的:你清楚地知道哪个变量拥有给定的值。然而,有些情况下,一个值可能拥有多个所有者。例如,在图数据结构中,多个边可能指向同一个节点,而该节点在概念上归属于所有指向它的边。除非一个节点没有任何指向它的边,即没有所有者,否则它不应该被清理。
In the majority of cases, ownership is clear: You know exactly which variable owns a given value. However, there are cases when a single value might have multiple owners. For example, in graph data structures, multiple edges might point to the same node, and that node is conceptually owned by all of the edges that point to it. A node shouldn’t be cleaned up unless it doesn’t have any edges pointing to it and so has no owners.
你必须使用 Rust 类型 Rc<T> 来显式启用多重所有权,它是“引用计数 (reference counting)”的缩写。 Rc<T> 类型跟踪指向一个值的引用数量,以确定该值是否仍在使用。如果一个值的引用数量为零,则该值可以在不使任何引用变为无效的情况下被清理。
You have to enable multiple ownership explicitly by using the Rust type
Rc<T>, which is an abbreviation for reference counting. The Rc<T> type
keeps track of the number of references to a value to determine whether or not
the value is still in use. If there are zero references to a value, the value
can be cleaned up without any references becoming invalid.
把 Rc<T> 想象成家庭活动室里的电视机。当一个人进来打算看电视时,他会打开它。其他人也可以进屋看电视。当最后一个人离开房间时,他会关掉电视,因为它不再被使用了。如果有人在其他人还在看电视时关掉电视,剩下的观众肯定会有意见!
Imagine Rc<T> as a TV in a family room. When one person enters to watch TV,
they turn it on. Others can come into the room and watch the TV. When the last
person leaves the room, they turn off the TV because it’s no longer being used.
If someone turns off the TV while others are still watching it, there would be
an uproar from the remaining TV watchers!
当我们想在堆上分配一些数据供程序的多个部分读取,且无法在编译时确定哪一部分会最后用完该数据时,我们使用 Rc<T> 类型。如果我们知道哪一部分会最后完成,我们只需让该部分成为数据的所有者,编译时强制执行的常规所有权规则就会生效。
We use the Rc<T> type when we want to allocate some data on the heap for
multiple parts of our program to read and we can’t determine at compile time
which part will finish using the data last. If we knew which part would finish
last, we could just make that part the data’s owner, and the normal ownership
rules enforced at compile time would take effect.
注意 Rc<T> 仅用于单线程场景。当我们在第 16 章讨论并发时,我们将介绍如何在多线程程序中进行引用计数。
Note that Rc<T> is only for use in single-threaded scenarios. When we discuss
concurrency in Chapter 16, we’ll cover how to do reference counting in
multithreaded programs.
共享数据 (Sharing Data)
让我们回到示例 15-5 中的 cons list 示例。回想一下,我们使用 Box<T> 定义了它。这一次,我们将创建两个列表,它们都共享第三个列表的所有权。从概念上讲,这类似于图 15-3。
Let’s return to our cons list example in Listing 15-5. Recall that we defined
it using Box<T>. This time, we’ll create two lists that both share ownership
of a third list. Conceptually, this looks similar to Figure 15-3.
图 15-3:两个列表 b 和 c 共享第三个列表 a 的所有权
我们将创建包含 5 和 10 的列表 a 。然后,我们将创建另外两个列表:以 3 开始的 b 和以 4 开始的 c 。列表 b 和 c 随后都将继续指向包含 5 和 10 的第一个列表 a 。换句话说,两个列表将共享包含 5 和 10 的第一个列表。
We’ll create list a that contains 5 and then 10. Then, we’ll make two
more lists: b that starts with 3 and c that starts with 4. Both the b
and c lists will then continue on to the first a list containing 5 and
10. In other words, both lists will share the first list containing 5 and
10.
如示例 15-17 所示,尝试使用带有 Box<T> 的 List 定义来实现此场景是行不通的。
Trying to implement this scenario using our definition of List with Box<T>
won’t work, as shown in Listing 15-17.
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-17/src/main.rs}}
当我们编译这段代码时,会得到这个错误:
When we compile this code, we get this error:
{{#include ../listings/ch15-smart-pointers/listing-15-17/output.txt}}
Cons 变体拥有它们持有的数据,所以当我们创建列表 b 时, a 被移动到了 b 中, b 拥有了 a 。然后,当我们尝试在创建 c 时再次使用 a 时,是不允许的,因为 a 已经被移动了。
The Cons variants own the data they hold, so when we create the b list, a
is moved into b and b owns a. Then, when we try to use a again when
creating c, we’re not allowed to because a has been moved.
我们可以更改 Cons 的定义以持有引用,但那样我们就必须指定生命周期参数。通过指定生命周期参数,我们将指定列表中的每个元素至少与整个列表一样长。在示例 15-17 的元素和列表中确实如此,但并不是每个场景都如此。
We could change the definition of Cons to hold references instead, but then
we would have to specify lifetime parameters. By specifying lifetime
parameters, we would be specifying that every element in the list will live at
least as long as the entire list. This is the case for the elements and lists
in Listing 15-17, but not in every scenario.
相反,我们将更改 List 的定义,使用 Rc<T> 代替 Box<T> ,如示例 15-18 所示。每个 Cons 变体现在将持有一个值和一个指向 List 的 Rc<T> 。当我们创建 b 时,不是获取 a 的所有权,而是克隆 a 持有的 Rc<List> ,从而将引用计数从一增加到二,并让 a 和 b 共享该 Rc<List> 中的数据。在创建 c 时我们也克隆 a ,将引用计数从二增加到三。每次我们调用 Rc::clone ,指向 Rc<List> 内数据的引用计数就会增加,除非引用计数为零,否则数据不会被清理。
Instead, we’ll change our definition of List to use Rc<T> in place of
Box<T>, as shown in Listing 15-18. Each Cons variant will now hold a value
and an Rc<T> pointing to a List. When we create b, instead of taking
ownership of a, we’ll clone the Rc<List> that a is holding, thereby
increasing the number of references from one to two and letting a and b
share ownership of the data in that Rc<List>. We’ll also clone a when
creating c, increasing the number of references from two to three. Every time
we call Rc::clone, the reference count to the data within the Rc<List> will
increase, and the data won’t be cleaned up unless there are zero references to
it.
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-18/src/main.rs}}
}
我们需要添加一个 use 语句将 Rc<T> 引入作用域,因为它不在 prelude 中。在 main 中,我们创建了持有 5 和 10 的列表并将其存储在 a 中的一个新 Rc<List> 中。然后,当我们创建 b 和 c 时,我们调用 Rc::clone 函数并传入 a 中 Rc<List> 的引用作为实参。
We need to add a use statement to bring Rc<T> into scope because it’s not
in the prelude. In main, we create the list holding 5 and 10 and store it
in a new Rc<List> in a. Then, when we create b and c, we call the
Rc::clone function and pass a reference to the Rc<List> in a as an
argument.
我们本可以调用 a.clone() 而不是 Rc::clone(&a) ,但 Rust 的惯例是在这种情况下使用 Rc::clone 。 Rc::clone 的实现并不像大多数类型的 clone 实现那样对所有数据进行深拷贝。对 Rc::clone 的调用只会增加引用计数,这并不会花费太多时间。数据的深拷贝可能会花费很多时间。通过为引用计数使用 Rc::clone ,我们可以直观地将深拷贝类型的克隆与增加引用计数的克隆区分开来。当查找代码中的性能问题时,我们只需要考虑深拷贝克隆,而可以忽略对 Rc::clone 的调用。
We could have called a.clone() rather than Rc::clone(&a), but Rust’s
convention is to use Rc::clone in this case. The implementation of
Rc::clone doesn’t make a deep copy of all the data like most types’
implementations of clone do. The call to Rc::clone only increments the
reference count, which doesn’t take much time. Deep copies of data can take a
lot of time. By using Rc::clone for reference counting, we can visually
distinguish between the deep-copy kinds of clones and the kinds of clones that
increase the reference count. When looking for performance problems in the
code, we only need to consider the deep-copy clones and can disregard calls to
Rc::clone.
通过克隆增加引用计数 (Cloning to Increase the Reference Count)
Cloning to Increase the Reference Count
让我们更改示例 15-18 中的可行示例,以便我们可以看到当我们创建和丢弃对 a 中 Rc<List> 的引用时,引用计数是如何变化的。
Let’s change our working example in Listing 15-18 so that we can see the
reference counts changing as we create and drop references to the Rc<List> in
a.
在示例 15-19 中,我们将更改 main ,使其在列表 c 周围有一个内部作用域;然后,我们可以看到当 c 超出作用域时引用计数是如何变化的。
In Listing 15-19, we’ll change main so that it has an inner scope around list
c; then, we can see how the reference count changes when c goes out of
scope.
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-19/src/main.rs:here}}
}
在程序中引用计数发生变化的每个点,我们都打印引用计数,这是通过调用 Rc::strong_count 函数获得的。此函数命名为 strong_count 而非 count ,是因为 Rc<T> 类型也有一个 weak_count ;我们将在“使用 Weak<T> 防止引用循环”中看到 weak_count 的用途。
At each point in the program where the reference count changes, we print the
reference count, which we get by calling the Rc::strong_count function. This
function is named strong_count rather than count because the Rc<T> type
also has a weak_count; we’ll see what weak_count is used for in “Preventing
Reference Cycles Using Weak<T>”.
这段代码打印以下内容:
{{#include ../listings/ch15-smart-pointers/listing-15-19/output.txt}}
我们可以看到 a 中的 Rc<List> 初始引用计数为 1;然后,每次我们调用 clone 时,计数都会增加 1。当 c 超出作用域时,计数会减少 1。我们不需要像调用 Rc::clone 增加引用计数那样通过调用函数来减少引用计数:当 Rc<T> 值超出作用域时, Drop 特征的实现会自动减少引用计数。
We can see that the Rc<List> in a has an initial reference count of 1;
then, each time we call clone, the count goes up by 1. When c goes out of
scope, the count goes down by 1. We don’t have to call a function to decrease
the reference count like we have to call Rc::clone to increase the reference
count: The implementation of the Drop trait decreases the reference count
automatically when an Rc<T> value goes out of scope.
在这个例子中我们看不出的是,当 main 结束时 b 然后 a 超出作用域,计数变为 0, Rc<List> 被完全清理。使用 Rc<T> 允许单个值拥有多个所有者,且计数确保了只要任何所有者仍然存在,该值就保持有效。
What we can’t see in this example is that when b and then a go out of scope
at the end of main, the count is 0, and the Rc<List> is cleaned up
completely. Using Rc<T> allows a single value to have multiple owners, and
the count ensures that the value remains valid as long as any of the owners
still exist.
通过不可变引用, Rc<T> 允许你在程序的多个部分之间共享数据以仅进行读取。如果 Rc<T> 也允许你拥有多个可变引用,你可能会违反第 4 章讨论的借用规则之一:对同一地点的多个可变借用会导致数据竞争和不一致。但是能够修改数据非常有用!在下一节中,我们将讨论内部可变性模式以及 RefCell<T> 类型,你可以将其与 Rc<T> 结合使用,以应对这种不可变性限制。
Via immutable references, Rc<T> allows you to share data between multiple
parts of your program for reading only. If Rc<T> allowed you to have multiple
mutable references too, you might violate one of the borrowing rules discussed
in Chapter 4: Multiple mutable borrows to the same place can cause data races
and inconsistencies. But being able to mutate data is very useful! In the next
section, we’ll discuss the interior mutability pattern and the RefCell<T>
type that you can use in conjunction with an Rc<T> to work with this
immutability restriction.