Rc<T>
, the Reference Counted Smart Pointer
In the majority of cases, ownership is very clear: you know exactly which
variable owns a given value. However, this isn't always the case; sometimes,
you may actually need multiple owners. For this, Rust has a type called
Rc<T>
. Its name is an abbreviation for reference counting. Reference
counting means keeping track of the number of references to a value in order to
know if a value is still in use or not. If there are zero references to a
value, we know we can clean up the value without any references becoming
invalid.
To think about this in terms of a real-world scenario, it's like a TV in a family room. When one person comes in the room to watch TV, they turn it on. Others can also come in the room and watch the TV. When the last person leaves the room, they'll turn the TV off since it's no longer being used. If someone turns off the TV while others are still watching it, though, the people watching the TV would get mad!
Rc<T>
is for use 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
of our program using this data will finish using it last. If we knew which part
would finish last, we could make that part the owner of the data and the normal
ownership rules enforced at compile time would kick in.
Note that Rc<T>
is only for use in single-threaded scenarios; the next
chapter on concurrency will cover how to do reference counting in
multithreaded programs. If you try to use Rc<T>
with multiple threads,
you'll get a compile-time error.
Using Rc<T>
to Share Data
Let's return to our cons list example from Listing 15-5. In Listing 15-11, we're
going to try to use List
as we defined it using Box<T>
. First we'll create
one list instance that contains 5 and then 10. Next, we want to create two more
lists: one that starts with 3 and continues on to our first list containing 5
and 10, then another list that starts with 4 and also continues on to our
first list containing 5 and 10. In other words, we want two lists that both
share ownership of the third list, which conceptually will be something like
Figure 15-10:
Trying to implement this using our definition of List
with Box<T>
won't
work, as shown in Listing 15-11:
Filename: src/main.rs
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
let a = Cons(5,
Box::new(Cons(10,
Box::new(Nil))));
let b = Cons(3, Box::new(a));
let c = Cons(4, Box::new(a));
}
If we compile this, we get this error:
error[E0382]: use of moved value: `a`
--> src/main.rs:13:30
|
12 | let b = Cons(3, Box::new(a));
| - value moved here
13 | let c = Cons(4, Box::new(a));
| ^ value used here after move
|
= note: move occurs because `a` has type `List`, which does not
implement the `Copy` trait
The Cons
variants own the data they hold, so when we create the b
list it
moves a
to be owned by b
. Then when we try to use a
again when creating
c
, we're not allowed to since a
has been moved.
We could change the definition of Cons
to hold references instead, but then
we'd have to specify lifetime parameters and we'd have to construct elements of
a list such that every element lives at least as long as the list itself.
Otherwise, the borrow checker won't even let us compile the code.
Instead, we can change our definition of List
to use Rc<T>
instead of
Box<T>
as shown here in Listing 15-12:
Filename: src/main.rs
enum List {
Cons(i32, Rc<List>),
Nil,
}
use List::{Cons, Nil};
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, a.clone());
let c = Cons(4, a.clone());
}
Note that we need to add a use
statement for Rc
because it's not in the
prelude. In main
, we create the list holding 5 and 10 and store it in a new
Rc
in a
. Then when we create b
and c
, we call the clone
method on a
.
Cloning an Rc<T>
Increases the Reference Count
We've seen the clone
method previously, where we used it for making a
complete copy of some data. With Rc<T>
, though, it doesn't make a full copy.
Rc<T>
holds a reference count, that is, a count of how many clones exist.
Let's change main
as shown in Listing 15-13 to have an inner scope around
where we create c
, and to print out the results of the Rc::strong_count
associated function at various points. Rc::strong_count
returns the reference
count of the Rc
value we pass to it, and we'll talk about why this function
is named strong_count
in the section later in this chapter about preventing
reference cycles.
Filename: src/main.rs
# enum List {
# Cons(i32, Rc<List>),
# Nil,
# }
#
# use List::{Cons, Nil};
# use std::rc::Rc;
#
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("rc = {}", Rc::strong_count(&a));
let b = Cons(3, a.clone());
println!("rc after creating b = {}", Rc::strong_count(&a));
{
let c = Cons(4, a.clone());
println!("rc after creating c = {}", Rc::strong_count(&a));
}
println!("rc after c goes out of scope = {}", Rc::strong_count(&a));
}
This will print out:
rc = 1
rc after creating b = 2
rc after creating c = 3
rc after c goes out of scope = 2
We're able to see that a
has an initial reference count of one. Then each
time we call clone
, the count goes up by one. When c
goes out of scope, the
count is decreased by one, which happens in the implementation of the Drop
trait for 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 of references to the list
containing 5 and 10 is then 0, and the list is dropped. This strategy lets us
have multiple owners, as the count will ensure that the value remains valid as
long as any of the owners still exist.
In the beginning of this section, we said that Rc<T>
only allows you to share
data for multiple parts of your program to read through immutable references to
the T
value the Rc<T>
contains. If Rc<T>
let us have a mutable reference,
we'd run into the problem that the borrowing rules disallow that we discussed
in Chapter 4: two mutable borrows to the same place can cause data races and
inconsistencies. But mutating data is very useful! In the next section, we'll
discuss the interior mutability pattern and the RefCell<T>
type that we can
use in conjunction with an Rc<T>
to work with this restriction on
immutability.