Rc in Rust

Rust’s ownership rule is simple: every value has exactly one owner. When the owner goes out of scope, the value is dropped.

Most of the time, that’s exactly what we want. But sometimes a value really does have more than one owner, and no single one of them should decide when it gets cleaned up.

That’s what Rc<T> is for. We used it briefly in the RefCell post. Now let’s look at it properly.

The Problem

In the singly linked list post, each node owned the next one through a Box. That works because every node has at most one predecessor.

But what if two lists share the same tail?

Let’s try it with Box:

struct Node {
    value: i32,
    next: Option<Box<Node>>,
}

fn main() {
    let shared = Box::new(Node { value: 3, next: None });

    let a = Node { value: 1, next: Some(shared) };
    let b = Node { value: 2, next: Some(shared) };
}

This doesn’t compile:

error[E0382]: use of moved value: `shared`

The first Some(shared) moves the box into a. After that, shared is gone, so b can’t have it.

We could clone the node, but then a and b would point to two separate copies. We want them to share one node. And we can’t say which list owns it, because the node should live as long as either list still needs it. References won’t help either: something else would still have to own the node and outlive both lists.

Rc

Rc stands for reference counted. It puts the value on the heap, next to a counter that tracks how many owners it has.

Let’s swap Box for Rc:

use std::rc::Rc;

struct Node {
    value: i32,
    next: Option<Rc<Node>>,
}

fn main() {
    let shared = Rc::new(Node { value: 3, next: None });

    let a = Node { value: 1, next: Some(Rc::clone(&shared)) };
    let b = Node { value: 2, next: Some(Rc::clone(&shared)) };

    let a_tail = a.next.as_ref().unwrap();
    let b_tail = b.next.as_ref().unwrap();

    println!("{} -> {}", a.value, a_tail.value);
    println!("{} -> {}", b.value, b_tail.value);
    println!("same node: {}", Rc::ptr_eq(a_tail, b_tail));
    println!("owners: {}", Rc::strong_count(&shared));
}

This prints:

1 -> 3
2 -> 3
same node: true
owners: 3

Rc::clone doesn’t copy the node. It increments the counter and returns another pointer to the same allocation. Rc::ptr_eq confirms that both tails are the same node.

The count is 3 because there are three owners: shared, a.next, and b.next.

Rc::clone vs. .clone()

We could have written shared.clone() instead. It does exactly the same thing.

But .clone() usually means “make a copy”, which can be expensive. Writing Rc::clone(&shared) makes it obvious that we’re only bumping a counter. It’s a common convention, and the Rust Book uses it too.

When Is the Value Dropped?

Every time an Rc is dropped, the count goes down by one. When it reaches zero, the value itself is dropped.

Let’s watch it happen:

use std::rc::Rc;

struct Texture {
    name: String,
}

impl Drop for Texture {
    fn drop(&mut self) {
        println!("Dropping {}", self.name);
    }
}

fn main() {
    let a = Rc::new(Texture { name: String::from("grass") });
    println!("count = {}", Rc::strong_count(&a));

    {
        let b = Rc::clone(&a);
        println!("count = {}", Rc::strong_count(&b));
    }

    println!("count = {}", Rc::strong_count(&a));

    drop(a);
    println!("End of main");
}

This prints:

count = 1
count = 2
count = 1
Dropping grass
End of main

When b goes out of scope, the count drops back to 1, but the texture survives because a still owns it. Only when a is dropped does the count hit zero and the texture get cleaned up.

Here’s a fun detail: under the hood, Rc stores its count in a Cell<usize>. It’s the same interior mutability from the Cell post. That’s how Rc::clone can update the count through a shared reference.

Rc Is Read-Only

What if we try to change the shared node?

use std::rc::Rc;

struct Node {
    value: i32,
}

fn main() {
    let shared = Rc::new(Node { value: 3 });
    shared.value = 5;
}
error[E0594]: cannot assign to data in an `Rc`

Dereferencing an Rc<T> only gives us &T, never &mut T. That’s on purpose. If several owners could mutate the value freely, we’d have multiple mutable references to the same data, which is exactly what the borrow checker exists to prevent.

When we do need to mutate shared data, we combine Rc with interior mutability:

  • Rc<Cell<T>> for small Copy values.
  • Rc<RefCell<T>> for everything else.

We covered Rc<RefCell<T>> in the RefCell post. Rc handles who owns the value, and RefCell handles who can change it right now.

Clone on Write

There’s one more option. Rc::make_mut gives us a &mut T, but if other owners exist, it clones the value first, so they aren’t affected. (This requires T: Clone.)

use std::rc::Rc;

fn main() {
    let mut a = Rc::new(vec![1, 2, 3]);
    let b = Rc::clone(&a);

    Rc::make_mut(&mut a).push(4);

    println!("{:?}", a);
    println!("{:?}", b);
}

This prints:

[1, 2, 3, 4]
[1, 2, 3]

a got its own copy, and b still sees the original. If a had been the only owner, make_mut would have skipped the clone and mutated in place.

Rc Is Not Thread-Safe

Let’s try to share an Rc with another thread:

use std::rc::Rc;
use std::thread;

fn main() {
    let data = Rc::new(5);
    let data2 = Rc::clone(&data);

    thread::spawn(move || {
        println!("{}", data2);
    });
}
error[E0277]: `Rc<i32>` cannot be sent between threads safely

Remember that the counter is a Cell<usize>. Incrementing it is a plain read, add, and write, with no synchronization. If two threads cloned or dropped the same Rc at the same time, they could both read the same count and overwrite each other’s update. The count would be wrong, and the value could be freed while someone is still using it.

So Rust doesn’t let it happen. Rc doesn’t implement Send or Sync, and the compiler catches the mistake before the program ever runs.

For sharing across threads, there’s Arc<T>, the atomically reference counted version. We used it in the Mutex and RwLock post. It works the same way, but updates its counter with atomic CPU instructions, which are safe to use from several threads at once.

Why not always use Arc, then? Because atomic operations cost a little more. If your data never leaves one thread, Rc is the cheaper choice, and the compiler will tell you if that ever changes.

Reference Cycles

Rc has one weakness. What happens if two values own each other?

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

struct Node {
    name: String,
    next: RefCell<Option<Rc<Node>>>,
}

impl Drop for Node {
    fn drop(&mut self) {
        println!("Dropping {}", self.name);
    }
}

fn main() {
    let a = Rc::new(Node {
        name: String::from("a"),
        next: RefCell::new(None),
    });
    let b = Rc::new(Node {
        name: String::from("b"),
        next: RefCell::new(Some(Rc::clone(&a))),
    });

    *a.next.borrow_mut() = Some(Rc::clone(&b));

    println!("a: {}", Rc::strong_count(&a));
    println!("b: {}", Rc::strong_count(&b));
}

This prints:

a: 2
b: 2

And then nothing. “Dropping” is never printed.

b points to a, and then we point a back to b, creating a cycle. When main ends, the variables a and b are dropped, and each count goes from 2 to 1. But each node is still owned by the other, so neither count ever reaches zero. Both nodes stay in memory forever.

This is a memory leak. Rust considers leaks memory-safe, so the compiler doesn’t stop us. It’s up to us to avoid cycles.

The fix is Weak<T>, a pointer that doesn’t count as an owner. A Weak can point to a value without keeping it alive. That’s the topic of a future post.

Wrapping Up

Rc<T> gives a value multiple owners. It keeps a count of them and drops the value when the last one goes away.

To recap:

  • Rc::clone is cheap. It bumps a counter instead of copying the data.
  • Rc<T> only gives shared access. Pair it with Cell or RefCell to mutate, or use Rc::make_mut for clone-on-write.
  • Rc is single-threaded. Use Arc to share across threads.
  • Cycles of Rc leak memory. Weak breaks them.

Further reading