Skip to content

Intermediate · Closures, iterators and smart pointers · topic 34 of 50 · 35 min

Rc, RefCell and Weak

Explanation

Rc<T> (reference counted) lets several owners share one value; it is freed when the last owner is dropped. RefCell<T> allows changing a value through a shared reference by checking the borrowing rules at run time instead of compile time, panicking if they are broken. Rc<RefCell<T>> gives shared, changeable data. Reference cycles leak memory; Weak<T> breaks them. Both Rc and RefCell are for a single thread.

When one owner is not enough

Ownership says each value has one owner. Sometimes data really has several owners: in a social app, a post may appear in many users' feeds; in a graph, several nodes point to the same node. Which owner should free it? The answer: whoever is last. Rc<T>, short for reference counted, does exactly this.

rust
use std::rc::Rc;

fn main() {
    let post = Rc::new(String::from("Diwali sale starts today!"));
    let feed_a = Rc::clone(&post);     // count = 2
    let feed_b = Rc::clone(&post);     // count = 3
    println!("{}", Rc::strong_count(&post));   // 3
    drop(feed_a);
    println!("{}", Rc::strong_count(&post));   // 2
    println!("{feed_b}");
}   // the last owners are dropped: count reaches 0, the String is freed
Rc::clone does not copy the String; it only increases a counter.
output
post   --+
feed_a --+--> [ count: 3 | "Diwali sale starts today!" ]   (heap)
feed_b --+
All three point to one heap block that also stores the count.

Rc only gives shared (read-only) access: you cannot get a &mut through it, because other owners might be reading. Rc is for one thread only; its counter is not safe to update from several threads (Arc, in module 9, is the thread-safe version).

RefCell: borrowing rules at run time

Sometimes you know a change is safe, but the compiler cannot prove it. RefCell<T> moves the borrow check from compile time to run time. Through a shared reference to a RefCell, you call borrow() to get read access or borrow_mut() to get write access. RefCell counts the active borrows, and if you break the rules (for example two borrow_mut at once), it panics. This pattern is called interior mutability: changing the inside of something that looks immutable from outside.

rust
use std::cell::RefCell;

let cart = RefCell::new(vec![120, 450]);
cart.borrow_mut().push(99);              // change through a shared binding
println!("{:?}", cart.borrow());          // [120, 450, 99]

let r1 = cart.borrow_mut();
// let r2 = cart.borrow_mut();            // PANIC at run time: already mutably borrowed
drop(r1);
Same rules as always (many readers or one writer), just checked while the program runs.

Rc<RefCell<T>>: shared and changeable

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

fn main() {
    let wallet = Rc::new(RefCell::new(1000));
    let parent = Rc::clone(&wallet);
    let child = Rc::clone(&wallet);

    *child.borrow_mut() -= 250;        // child spends
    *parent.borrow_mut() += 500;       // parent tops up
    println!("{}", wallet.borrow());    // 1250
}
Rc gives several owners; RefCell lets each of them change the value.

Reference cycles and Weak

If A holds an Rc to B and B holds an Rc to A, neither count can reach zero, so neither is ever freed: a memory leak. Safe Rust prevents use-after-free, but it does not prevent leaks. The fix is Weak<T>: a non-owning pointer that does not keep the value alive. You make one with Rc::downgrade(&rc). To use it, call upgrade(), which returns Some(Rc) if the value still exists and None if it was freed.

output
parent --Rc--> child          (parent owns child: strong)
child  --Weak-> parent         (child only refers back: weak)
Typical tree: strong pointers down, weak pointers up.

Which pointer when?

NeedUse
One owner, value on the heapBox<T>
Several owners, read-only, one threadRc<T>
Change through a shared reference, one threadRefCell<T> (or Cell<T> for Copy values)
Several owners that change data, one threadRc<RefCell<T>>
Back-references without ownershipWeak<T>
Several owners across threadsArc<T>, with Mutex<T> to change (module 9)

Common mistakes

  • Writing post.clone() on an Rc and thinking it copies the data. It does not, but Rc::clone(&post) makes this clearer to readers.
  • Holding a borrow_mut() and calling a function that calls borrow() on the same RefCell: a run-time panic "already mutably borrowed". Keep RefCell borrows short.
  • Using Rc<RefCell<T>> everywhere to escape the borrow checker. It works but moves errors to run time; restructure first.
  • Sending an Rc to another thread: the compiler refuses because Rc is not Send. Use Arc.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Rc, RefCell and Weak · 1/7

Rc<T> = shared ownership

Sometimes a value has several owners. Rc, reference counted, keeps a count and frees the value when the last owner is gone.

Prefer a video by a teacher? Find free videos on “rust rc refcell weak interior mutability explained” on YouTube ↗

In real life: A shared shopping cart for a family

A grocery app lets family members add to one shared cart from different screens of the same app session. Each screen holds an Rc to the cart, so the cart lives as long as any screen needs it, and RefCell lets each screen add items. Because everything runs on the app's single UI thread, Rc and RefCell are enough.

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

struct Screen { name: &'static str, cart: Rc<RefCell<Vec<String>>> }

impl Screen {
    fn add(&self, item: &str) {
        self.cart.borrow_mut().push(format!("{item} (by {})", self.name));
    }
}

fn main() {
    let cart = Rc::new(RefCell::new(Vec::new()));
    let mom = Screen { name: "Mom", cart: Rc::clone(&cart) };
    let son = Screen { name: "Son", cart: Rc::clone(&cart) };
    mom.add("milk");
    son.add("chips");
    println!("{:?}", cart.borrow());
    println!("owners: {}", Rc::strong_count(&cart));   // 3
}
Prints the two items with who added them, then owners: 3.

Practice (0/3 done)

Task 1 · Question

What does this print? use std::rc::Rc; let a = Rc::new(5); let b = Rc::clone(&a); { let c = Rc::clone(&a); print!("{} ", Rc::strong_count(&a)); } println!("{}", Rc::strong_count(&b));

Task 2 · Question

What happens if you call borrow_mut() twice on the same RefCell while the first borrow is still alive?

Task 3 · Practice

Model a WhatsApp-style group: struct Group { messages: RefCell<Vec<String>> } shared by three Member structs through Rc<Group>. Each member posts a message. Print all messages and the strong count. Then explain in two lines why a Member holding Rc<Group> and the Group holding Rc<Member> would leak, and how Weak fixes it.

Quick quiz

Question 1

1.When is the value inside an Rc freed?

Question 2

2.What is interior mutability?

Question 3

3.Why can a cycle of Rc pointers leak memory?

Question 4

4.What does Weak::upgrade() return?

Question 5

5.Which type should you use for shared ownership across threads?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

When is the value inside an Rc freed?

Answer: When the strong count reaches zero

The last owner to be dropped frees it.

What is interior mutability?

Answer: Changing a value through a shared reference, with checks moved to run time

RefCell and Cell provide interior mutability.

Why can a cycle of Rc pointers leak memory?

Answer: Neither count can reach zero, so nothing is freed

Each keeps the other alive.

What does Weak::upgrade() return?

Answer: Some(Rc) if the value still exists, else None

A Weak may point to a value that has been freed.

Which type should you use for shared ownership across threads?

Answer: Arc

Arc uses atomic counting, so it is thread-safe.

Many threads read a config and rarely update it. Which is a good fit?Related

Answer: RwLock

RwLock allows many readers at once.

From Shared state: Arc, Mutex, Send and Sync

Is RefCell<T> safe to share between threads?Related

Answer: No, it is not Sync

Use Mutex or RwLock across threads.

From Interview questions: traits, errors, pointers and concurrency

Why does Rust forbid a &mut while & references exist?Related

Answer: So readers never see the value change under them (no data races)

Exclusive access for writers prevents data races and similar bugs.

From References and borrowing

Does a weak_ptr keep its object alive?Related

Answer: No, it does not change the use count

weak_ptr observes without owning.

From Smart pointers 2: std::shared_ptr and std::weak_ptr

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next: Threads →

Next steps

  1. This is the last topic of “Closures, iterators and smart pointers”. The module exam comes next.
  2. Module 8 exam: Closures, iterators and smart pointers · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Concurrency and async. 50 topics left in the course before the final exam.

← Previous: Box<T>: values on the heap