Intermediate · Closures, iterators and smart pointers · topic 34 of 50 · 35 min
Rc, RefCell and Weak
Explanation
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.
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 freedpost --+
feed_a --+--> [ count: 3 | "Diwali sale starts today!" ] (heap)
feed_b --+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.
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);Rc<RefCell<T>>: shared and changeable
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
}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.
parent --Rc--> child (parent owns child: strong)
child --Weak-> parent (child only refers back: weak)Which pointer when?
| Need | Use |
|---|---|
| One owner, value on the heap | Box<T> |
| Several owners, read-only, one thread | Rc<T> |
| Change through a shared reference, one thread | RefCell<T> (or Cell<T> for Copy values) |
| Several owners that change data, one thread | Rc<RefCell<T>> |
| Back-references without ownership | Weak<T> |
| Several owners across threads | Arc<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
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.
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
}Practice (0/3 done)
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));
What happens if you call borrow_mut() twice on the same RefCell while the first borrow is still alive?
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- IteratorsClosures, iterators and smart pointers · same module
- ClosuresClosures, iterators and smart pointers · same module
- Shared state: Arc, Mutex, Send and SyncConcurrency and async
- Interview questions: traits, errors, pointers and concurrencyRust careers and interviews
- Smart pointers 2: std::shared_ptr and std::weak_ptrC++ Programming (modern C++ and DSA) · Templates, smart pointers and move semantics
- Interview questions: ownership, borrowing and lifetimesRust careers and interviews
Questions people ask about this
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
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.
Does a weak_ptr keep its object alive?Related
Answer: No, it does not change the use count
weak_ptr observes without owning.
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 steps
- This is the last topic of “Closures, iterators and smart pointers”. The module exam comes next.
- Module 8 exam: Closures, iterators and smart pointers · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Concurrency and async. 50 topics left in the course before the final exam.