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

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 Rc value 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:

  • a owns one Rc<String> value.
  • b owns another Rc<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:

  1. 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.
  2. 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 several Rc values share the same heap data.
  • Rc::new(value) starts the count at 1.
  • .clone() creates another Rc value for the same heap data: count +1, no new independent inner value.
  • Dropping an Rc decrements the count; the heap data is released when the count reaches zero.
  • Rc<T> by itself provides shared read access, not unrestricted mutation.
  • Rc shares safely thanks to two things: read-only access, plus a counter that keeps the data alive until the last Rc is gone.
  • Rc<T> has restrictions; it is not the general answer for every sharing problem.
  • Each Rc value still follows ordinary ownership rules: it moves and drops like any other non-Copy value.
  • Check the current reference count with Rc::strong_count(&x).