Rc<T>
Goal of This Episode
Learn to let several Rc<T> values share the same heap data, and understand how reference counting keeps that data alive.
Concept
Last episode was Box<T>: one safe, one key. The Box value is the key, and the heap data is the thing inside the safe.
But sometimes several parts of your program need to use the same heap data without creating another independent heap value.
The Problem: Moving a Box
With Box<T>, there is only one key. Assigning it to another variable moves that key:
fn main() {
let a = Box::new(String::from("hello"));
let b = a; // Move! a can't be used anymore
}
After this move, b has the key. The heap data was not cloned, and a can no longer use it.
What if you want both a and b to keep using the same text?
You might think: “Just call .clone(), right?”
fn main() {
let a = Box::new(String::from("hello"));
let b = a.clone(); // Creates a new Box with another String
}
That does let both a and b work. But in this Box<String> example, .clone() creates another Box holding a new String with the same contents. If the inner value is large, that can be a real expense. And a and b now use two separate String values, not the same one.
If what you need is “several values using the same heap data,” Box’s .clone() is the wrong tool.
Rc’s .clone() Really Does Just Cut an Extra Key
Remember Chapter 4’s definition of .clone()? — “get a new keychain that works just like the original, while making sure that doing so causes no trouble.” For simple owned types like String or Vec<i32>, the way to “make sure” is to create another key to another safe with the same contents.
Rc is the exception Chapter 4 foreshadowed: its .clone() really does just cut an extra key. You get a new Rc value that opens the same safe. The data itself is not replicated, which is why calling .clone() on an Rc stays cheap when the data is large.
But if several keys can open the same safe, Rust needs a way to know when the safe can be released. That’s where the counter comes in.
Rc: Reference Counting
Rc<T> stands for reference counting.
Think of Rc<T> as a safe that allows several keys. Rc keeps a counter for that safe.
That counter tracks how many Rc<T> values can still open the safe:
- Creating the first
Rc: count = 1. .clone(): count +1 — you get another key to the same safe, not another independent set of the data.- An
Rcvalue leaving scope: count -1. - Only when the count reaches zero is the heap data released.
What the counter handles is when the safe can be released: as long as someone still holds a key, the safe stays, so nobody is left holding a key to a safe that is already gone.
This also means Rc’s Clone cannot be derived. Rc’s .clone() has two jobs: cut another key, and bump the counter on the safe. derive only calls .clone() on every field — it can do the first job, not the second, so you would end up with an extra key while the count stays put. That’s why the standard library hand-writes Clone for Rc.
Two Layers to Keep Separate
This distinction matters:
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("hello"));
let b = a.clone();
}
There are two layers here:
aowns oneRc<String>value.bowns anotherRc<String>value.- Both
Rc<String>values open the same heap data. - The counter tracks how many
Rc<String>values still exist.
So Rc does not mean ordinary Rust values stop following the ownership rules. Each Rc value is still an ordinary value: it can move, it can be dropped, and after moving it the old variable cannot be used. The special part is the heap data behind those Rc values: it stays alive until the last key is gone.
Rc Is Read-only
Rc<T> by itself provides shared read access. Several Rc values can read the same heap data, but they cannot freely modify it.
If you need shared data that can also be modified, RefCell<T> comes later.
Wait — Didn’t Chapter 4 Warn Against Duplicating Keys?
Chapter 4’s worry was very concrete: two people each hold a key to the same safe, A is tidying up what’s inside, and B walks off with the contents. So what earns Rc the right to cut several keys?
Because Rc blocks both of the dangers in that scene:
- Nobody can touch what’s inside:
Rc<T>only grants shared read access. Chapter 4’s fear was one person tidying while another takes things away; if everyone can only look, that cannot happen. - The safe is not released while someone still holds a key: that’s the counter’s job. The data is freed only once the last key is gone.
The second point is not free: the counter must update on every clone and drop — overhead Box<T> doesn’t have. Box<T> is the simple one-key heap setup with no counter; Rc<T> adds runtime bookkeeping so several Rc values can open the same safe. That sharing is useful, but it is not free.
And when we get to multithreading, we’ll see that Rc has other restrictions there. Just know this for now.
Example Code
use std::rc::Rc;
fn main() {
// Create an Rc; count = 1
let a = Rc::new(String::from("shared data"));
println!("Created a, count = {}", Rc::strong_count(&a));
// .clone() creates another Rc for the same heap data
let b = a.clone();
println!("Cloned to b, count = {}", Rc::strong_count(&a));
let c = a.clone();
println!("Cloned to c, count = {}", Rc::strong_count(&a));
// a, b, and c can all read the same data
println!("a = {}", a);
println!("b = {}", b);
println!("c = {}", c);
{
let _d = a.clone();
println!("Inside the scope, count = {}", Rc::strong_count(&a));
} // _d is dropped; count -1
println!("After leaving the scope, count = {}", Rc::strong_count(&a));
// A practical use: several values sharing the same name
let shared_name = Rc::new(String::from("Rust"));
let greeting1 = shared_name.clone();
let greeting2 = shared_name.clone();
println!("1: {}", greeting1);
println!("2: {}", greeting2);
}
Recap
Rc<T>lets severalRcvalues share the same heap data.Rc::new(value)starts the count at 1..clone()creates anotherRcvalue for the same heap data: count +1, no new independent inner value.- Dropping an
Rcdecrements the count; the heap data is released when the count reaches zero. Rc<T>by itself provides shared read access, not unrestricted mutation.Rcshares safely thanks to two things: read-only access, plus a counter that keeps the data alive until the lastRcis gone.Rc<T>has restrictions; it is not the general answer for every sharing problem.- Each
Rcvalue still follows ordinary ownership rules: it moves anddrops like any other non-Copyvalue. - Check the current reference count with
Rc::strong_count(&x).