Before you begin
Why this matters
In Python or Java, every object can be shared and changed by any reference to it. In Rust, sharing and changing are separate choices, written in the type. If you can read those types, you can predict when a shared value is freed, and which mistakes the compiler reports and which ones stop the program while it runs.
What this helps you reason about
- Say when a type needs a Box, and predict the owner count of an Rc and the moment its value is dropped.
- Change a shared value through a RefCell, and spot a borrow that will make the program stop with a panic.
- Recognize a cycle of Rc handles that is never freed, and break it with a Weak handle.
Rust gives every value one owner, and the compiler checks every borrow. Copying, identity, and aliasing showed one way to share: cloning an Rc makes a second handle to one value, and neither handle can change it. Some programs need more: a type that contains itself, a value that two parts of the program own, or shared data that must change. This lesson takes each wrapper in the standard library that does one of these jobs, and shows what it costs.
Already know this? The short version
- Box<T> is one owner of a value on the heap; a type that contains itself needs one (E0072). Rc<T> gives a value several owners in one thread: Rc::clone adds one to the strong count, each dropped handle takes one away, and the value is dropped at 0.
- RefCell<T> allows changes through a shared reference. borrow and borrow_mut return guards, counted while the program runs; a borrow_mut while any guard is alive panics with already borrowed: BorrowMutError. A guard in a match scrutinee lives until the end of the match. Rc<RefCell<T>> is a shared value that every handle can change.
- A cycle of Rc handles is never dropped. Make the link back toward the owner a Weak, from Rc::downgrade; upgrade returns an Option. Cell<T> does get and set for Copy values; Arc and Mutex are Rc and RefCell for several threads.
- The section “What goes wrong with Rc and RefCell in real programs?” covers a borrow that lasts for a whole match, cycles through parent links, Rc<RefCell<T>> used to silence the compiler, and an Rc sent to a thread.
Words in this lesson (8)
- Box
- A small value that owns one value stored on the heap. Box::new(3) puts 3 on the heap and returns the Box that owns it.
- Rc
- A handle, Rc<T> from std::rc, to a value stored on the heap. Rc::clone makes a second handle to the same value, not a copy of it, and while two handles share the value, neither can change it.
- strong count
- The number of Rc handles that own one value, read with Rc::strong_count(&handle). Rc::clone adds one, dropping a handle takes one away, and at 0 the value is dropped.
- interior mutability
- Changing a value through a shared reference. Rust allows it only inside types such as Cell and RefCell, which check or limit the access themselves.
- RefCell
- A wrapper, RefCell<T> from std::cell, whose value can be changed through a shared reference. borrow and borrow_mut follow the borrowing rules, checked while the program runs: breaking them causes a panic.
- Weak
- A handle, Weak<T> from std::rc, made from an Rc by Rc::downgrade. It does not keep the value alive: upgrade returns Some with an Rc while the value exists, and None after it is dropped.
- Cell
- A wrapper, Cell<T> from std::cell, for a Copy value such as a number. get copies the value out and set replaces it, through a shared reference, with no borrows to check.
- Arc
- The Rc for several threads, Arc<T> from std::sync. It changes its count with atomic operations, which other threads cannot interrupt halfway, so its handles can be sent to other threads.
Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.
01 / Box
When does a value need a Box?
A Box is a small value that owns one value stored on the heap, as Pointers and dereferencing showed: Box::new(3) puts 3 on the heap and returns the Box that owns it. A Box has exactly one owner. Moving the Box moves the ownership, and dropping the Box drops the value on the heap.
Most code needs no Box, because a String or a Vec already keeps its contents on the heap. A Box is needed when the compiler must know the size of a value and cannot. The common case is a type that contains itself. Below, a Route is either a Stop, which has a place name and the rest of the route, or the End. Without the Box, a Stop would hold a whole Route inside it, which could hold another Stop, without end. The compiler reports:
error[E0072]: recursive type `Route` has infinite size
with “recursive without indirection” under the inner Route, and help that suggests a Box. With Box<Route>, a Stop holds one address, which has a fixed size, and the rest of the route is on the heap.
A Box also holds values whose type is known only through a trait, such as Box<dyn Fn(u32) -> u32> in Rust closure capture and Fn traits.
enum Route {
Stop(&'static str, Box<Route>),
End,
}
let osaka = Route::Stop("Osaka", Box::new(Route::End));
// osaka moves into the Box; nara owns the whole route
let nara = Route::Stop("Nara", Box::new(osaka)); A Box is one owner of one value on the heap; a type that contains itself needs one.
Coming from Java: A Java class can have a field of its own type, because every field of a class type holds a reference; in Rust a field holds the value itself unless you write a Box or another handle.
Precisely: what a Box costs
A Box<T> is one address wide, so a Stop has a known size whatever the length of the route. Box::new allocates heap memory, and dropping the Box frees it. *boxed reaches the value, and methods and fields are reached through a Box without writing *. The E0072 help also names Rc and &, which hold an address in the same way. The error messages in this lesson are quoted as rustc 1.63, the stepper’s compiler, prints them.
Experiment
Follow a route from one Box to the next
A Stop holds the rest of the route. Without a Box, a Stop would contain a whole Route, which could contain another Stop, and so on, so the compiler cannot give Route a size. With a Box, a Stop holds one address, and the rest of the route lives on the heap.
Why it matters When a type contains itself, put the inner one in a Box. The Box has one owner, and dropping the outer value drops every stop after it.
Predict what prints, and what the stepper shows for the Box inside nara. Then change Box<Route> to Route in the second line, and predict whether the program still compiles.
enum Route {
Stop(&'static str, Box<Route>),
End,
}
fn count(route: &Route) -> u32 {
match route {
Route::Stop(_, rest) => 1 + count(rest),
Route::End => 0,
}
}
fn main() {
let osaka = Route::Stop("Osaka", Box::new(Route::End));
let nara = Route::Stop("Nara", Box::new(osaka));
println!("{} stops", count(&nara));
}
What to look for
At the println! line, nara shows Stop("Nara" (len=4, …), 0x… → Stop("Osaka" …, 0x… → End)). The stepper shows a variant such as Stop with its fields in order, and the Box is the second field: one address, then → and the Route it leads to. osaka is still listed with its old bytes, although it moved into nara’s Box; as in Rust ownership and moves, that is not a second owner. Step into count. In the first count frame, route is an address in main’s frame, where nara is. In the second, route is the address of nara’s Box: the Box led there. The third frame’s route is the address of osaka’s Box, and it shows End. On this run, heap addresses start with 0xaaaa and main’s frame with 0xffff; that is a detail of this build. The run prints 2 stops. After the edit, the compiler reports error[E0072]: recursive type `Route` has infinite size, with “recursive without indirection” under Route and help that suggests a Box. Two error[E0308]: mismatched types follow, because the calls still pass a Box.
Expected output
2 stops02 / Rc
How can one value have several owners?
Copying, identity, and aliasing showed that Rc::clone makes a second handle to one value, not a copy of it. This section counts those handles.
Rc::new(value) puts the value on the heap together with a strong count of 1. The strong count is the number of Rc handles that own the value. Rc::clone adds 1, and dropping a handle takes 1 away. When the count reaches 0, the value is dropped: its drop method runs, if its type implements the Drop trait from Cleanup on a failing path, and its memory is freed. Rc::strong_count(&handle) reads the count.
So with Rc, no single binding decides when the value ends. In the code below, mei and tom both own the plan. tom goes away first, and the plan stays, because mei still owns it. The plan is dropped when mei goes too.
Use an Rc when several parts of a program must own one value, and you cannot say which part will finish last. When one part clearly lives longest, let it own the value, and lend references to the others.
let mei = Rc::new(Plan { city: "Kyoto" });
{
let tom = Rc::clone(&mei);
// 2: mei and tom own one plan
println!("{}", Rc::strong_count(&mei));
}
// 1: tom was dropped; the plan stays
println!("{}", Rc::strong_count(&mei)); Rc::clone adds an owner; the value is dropped when the strong count reaches 0, after the last handle goes.
Coming from Python: CPython counts the references to every object; Rust counts only the values you put in an Rc, and frees each one at the exact moment its count reaches 0.
Coming from C++: Rc is close to std::shared_ptr, but its count is not atomic, and the compiler stops you from sending it to another thread.
Precisely: where the counts live
Rc::new allocates one heap block that holds the strong count, the weak count used by Weak, and the value. Each handle is one address, and the address the stepper shows after Rc(strong=…) is the start of that block; on this 64-bit build the value starts 16 bytes later, after the two counts. Rc::clone(&a) and a.clone() do the same thing; writing Rc::clone makes clear that no value is copied. Rc::ptr_eq, used in Immutable updates and persistent data, tells whether two handles lead to one block. An Rc is for one thread only: its count is changed with ordinary additions.
Experiment
Count the owners of one plan
Rc::new puts the Plan on the heap with a strong count of 1. Rc::clone makes a second handle to the same block and adds 1. Dropping tom subtracts 1. The Plan is dropped when the count reaches 0, after mei, the last handle, goes out of scope. Rc::new instead makes a second Plan with its own count.
Why it matters Read the strong count to know when a shared value will be dropped: at the moment the last handle goes, not when the first owner does.
Predict the four output lines, and which line the plan is freed after. Then change Rc::clone(&mei) to Rc::new(Plan { city: "Kyoto" }) and predict again.
use std::rc::Rc;
struct Plan {
city: &'static str,
}
impl Drop for Plan {
fn drop(&mut self) {
println!("plan for {} freed", self.city);
}
}
fn main() {
let mei = Rc::new(Plan { city: "Kyoto" });
println!("owners: {}", Rc::strong_count(&mei));
{
let tom = Rc::clone(&mei);
println!("owners: {} ({})", Rc::strong_count(&mei), tom.city);
}
println!("owners: {}", Rc::strong_count(&mei));
}
What to look for
An Rc shows as Rc(strong=1) 0x… → {city: "Kyoto"}: the strong count, the address of the heap block that holds the counts and the Plan, and the Plan. After let tom, mei and tom show the same address, and both show strong=2. At line 20, tom is no longer in main’s frame, and mei shows strong=1 again. After the last line, the stepper stops in a frame named main::{impl#0}::drop, the drop method of the first impl block, and main’s frame lists nothing. The run prints owners: 1, owners: 2 (Kyoto), owners: 1, then plan for Kyoto freed. After the edit, tom shows a different address, each Rc shows strong=1, and the run prints owners: 1, owners: 1 (Kyoto), and plan for Kyoto freed appears twice: once when tom’s plan is dropped at the inner closing brace, and once at the end.
Expected output
owners: 1
owners: 2 (Kyoto)
owners: 1
plan for Kyoto freed03 / RefCell
How can a shared value change?
Rc shares a value for reading only, as a shared reference does. tom.push("train") on an Rc<Vec<&str>> gives:
error[E0596]: cannot borrow data in an `Rc` as mutable
Changing a value through a shared reference is called interior mutability. Rust allows it only inside types that check or limit the access themselves. RefCell<T>, from std::cell, is the usual one. borrow() returns a read guard, and borrow_mut() returns a write guard. A guard is a value that gives access to the inside of the RefCell and counts as a borrow until it is dropped: at the end of its block, at the end of its statement if it was never stored, or when you pass it to drop. drop(x) is a standard function that takes ownership of x and does nothing, so x is dropped at once.
The rule is the one from Rust borrowing and references: many readers or one writer. The difference is when it is checked. The compiler checks references before the program runs. A RefCell counts its guards while the program runs. borrow_mut while any guard is alive panics with already borrowed: BorrowMutError, and borrow while a write guard is alive panics with already mutably borrowed: BorrowError.
Rc<RefCell<T>> puts the two together: several owners, and each can change the value. Every handle sees every change.
let bookings = Rc::new(RefCell::new(vec!["hostel"]));
let tom = Rc::clone(&bookings);
// the write guard is dropped at the end of this statement
tom.borrow_mut().push("train");
// ["hostel", "train"]
println!("{:?}", bookings.borrow()); A RefCell checks the borrowing rules while the program runs: the same rule as the compiler’s, with a panic instead of an error.
Coming from Java: Any Java reference can change the object it points to; in Rust you choose this for one value by wrapping it in a RefCell, and every access is counted while the program runs.
Precisely: the counter and the try_ methods
A RefCell keeps its counter next to the value. In the stepper it is the number after borrow=: a positive number counts read guards, -1 means one write guard, and 0 means no borrow. try_borrow and try_borrow_mut return a Result instead of panicking, for code that can do something else when the value is busy. Because nothing is checked at compile time, a borrow mistake shows up only on the run that takes that path, so test the paths that write.
Experiment
Hold a read guard while a function writes
borrow() returns a guard that counts as a reader until it is dropped. borrow_mut() checks the counter while the program runs and panics if any reader or writer is alive. The compiler does not see the conflict: both calls go through a shared reference, &Trip, which is allowed.
Why it matters A RefCell turns a borrow error into a panic at run time. End each guard before code that may write, even code in another function.
Predict both output lines. Then delete the line drop(list);, and predict whether the program still compiles, and what happens when it runs.
use std::cell::RefCell;
struct Trip {
bookings: RefCell<Vec<&'static str>>,
}
fn book(trip: &Trip, item: &'static str) {
trip.bookings.borrow_mut().push(item);
}
fn main() {
let trip = Trip { bookings: RefCell::new(vec!["hostel"]) };
let list = trip.bookings.borrow();
println!("{} booked", list.len());
drop(list);
book(&trip, "train");
println!("{:?}", trip.bookings.borrow());
}
What to look for
trip shows {bookings: RefCell(borrow=0) ["hostel"] …}: the number after borrow= is the RefCell’s borrow counter. After let list, it shows borrow=1: one read guard is alive. After drop(list), it is back to 0; list is still listed with its old bytes, but it has been moved into drop. Step into book: there trip is an address, and the Trip it leads to shows borrow=0. The run prints 1 booked, then ["hostel", "train"]. After the edit, the program compiles. In book, the counter still shows borrow=1, and the run ends with an error: thread 'main' panicked at 'already borrowed: BorrowMutError', main.rs:8:19, which points at borrow_mut in book. The output so far is 1 booked.
Expected output
1 booked
["hostel", "train"]04 / Weak
What keeps a shared value alive too long?
An Rc value is dropped when its strong count reaches 0. If two values hold Rc handles to each other, each one keeps the other's count above 0, so neither is ever dropped. This is a cycle. Its memory is not freed, and its Drop code never runs, until the program ends. Safe Rust prevents use of freed memory, but it does not promise that every value is freed.
Weak<T>, from std::rc, is a handle that does not own. Rc::downgrade(&rc) makes one, and adds 1 to the weak count, not to the strong count. Because a Weak handle does not own, the value may already be gone when you use it. So upgrade() returns an Option: Some with a new Rc while the value exists, and None after it was dropped.
Decide which direction owns. Below, a group owns its members with Rc handles, and each member points back to its group with a Weak handle. When the last Rc to the group goes, the group is dropped, its list of members with it, and a member’s upgrade returns None.
struct Traveller {
name: &'static str,
// not an owner: the group owns its members
group: Weak<Group>,
}
match mei.group.upgrade() {
Some(g) => println!("{} is with {}", mei.name, g.name),
None => println!("{} has no group", mei.name),
} A cycle of Rc handles is never dropped; make the link back toward the owner a Weak, and handle None from upgrade.
Coming from Java: Java’s garbage collector frees a group and its members that point to each other once nothing else reaches them; reference counting cannot see that, so in Rust you break the cycle with Weak.
Experiment
Let a group go while a member points back
With Weak, mei’s link does not count, so drop(group) takes the strong count to 0 and the Group is dropped. With Rc, the Group and mei own each other. Dropping group takes its count from 2 to 1, and nothing can take it to 0, so neither value is ever dropped.
Why it matters Give the link that points back toward an owner a Weak handle; a cycle of Rc handles is never freed.
Predict the three output lines. Then change Weak<Group> to Rc<Group> in the Traveller struct, and Rc::downgrade(&group) to Rc::clone(&group), and predict again.
use std::cell::RefCell;
use std::rc::{Rc, Weak};
struct Group {
name: &'static str,
members: RefCell<Vec<Rc<Traveller>>>,
}
struct Traveller {
group: Weak<Group>,
}
impl Drop for Group {
fn drop(&mut self) {
println!("group {} freed", self.name);
}
}
fn main() {
let group = Rc::new(Group {
name: "Nara",
members: RefCell::new(Vec::new()),
});
let mei = Rc::new(Traveller { group: Rc::downgrade(&group) });
group.members.borrow_mut().push(Rc::clone(&mei));
let strong = Rc::strong_count(&group);
let weak = Rc::weak_count(&group);
println!("group: strong {}, weak {}", strong, weak);
drop(group);
println!("mei: strong {}", Rc::strong_count(&mei));
}
What to look for
group and mei show as Rc(strong=1) 0x… → {…}, each with its own address. mei’s group field, the Weak, still shows raw internals; its pointer: is group’s address. After the push, mei shows strong=2: the list holds the second handle. At the println! line, strong is 1 and weak is 1: only group owns the Group, and mei’s link is not an owner. drop(group) steps into main::{impl#0}::drop, the Group’s drop method. Back in main, group is still listed with its old address and strong=0, because it was moved into drop. At the last line, mei shows strong=1. The run prints group: strong 1, weak 1, then group Nara freed, then mei: strong 1: dropping the Group dropped its members list, and with it one of mei’s owners. After the edit, the run prints group: strong 2, weak 0, then mei: strong 2, and never group Nara freed: the stepper never enters drop.
Expected output
group: strong 1, weak 1
group Nara freed
mei: strong 105 / Cell, Arc, Mutex
What do Cell, Arc, and Mutex add?
Cell<T>, from std::cell, is interior mutability for Copy values such as numbers. get copies the value out, and set puts a new one in. No reference to the inside is ever handed out, so there are no guards to count and nothing to panic about. A counter shared by several handles is often an Rc<Cell<u32>>.
A thread is a path of execution inside a program, with its own stack. Rc and RefCell are for one thread: they count with plain additions, which two threads could interleave. The compiler refuses to send them to another thread. The thread versions are Arc<T>, from std::sync, an Rc whose count uses atomic operations, and Mutex<T>, a RefCell for threads. lock() waits until no other thread holds the Mutex, then returns a guard inside a Result, so it is written lock().unwrap().
Below, thread::spawn starts a thread that runs a move closure, and join waits for it to finish. With Rc in place of Arc, the compiler reports error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely. Threads are covered later, with other kinds of concurrency.
let seats = Arc::new(Mutex::new(40));
let other = Arc::clone(&seats);
thread::spawn(move || {
*other.lock().unwrap() -= 3;
})
.join()
.unwrap();
*seats.lock().unwrap() -= 2;
// seats left: 35
println!("seats left: {}", seats.lock().unwrap()); Box: one owner. Rc: several owners in one thread. Cell and RefCell: changes through a shared handle in one thread. Arc and Mutex: the same jobs across threads.
Precisely: what Send and Sync say
Send is the trait for types whose values may move to another thread, and Sync for types that several threads may reach through shared references. Rc is neither. RefCell and Cell are Send when their contents are, and never Sync. Arc<T> is Send and Sync only when T is, which is why Arc<RefCell<T>> cannot be shared and Arc<Mutex<T>> can. A Mutex whose holder panicked is poisoned, and lock then returns an Err; that is why lock returns a Result.
06 / In production
What goes wrong with Rc and RefCell in real programs?
Four mistakes with these wrappers are common in larger programs. If a program panics with BorrowMutError, never runs a Drop, or gets E0277 at thread::spawn, look here.
A borrow that lasts for a whole match. A guard made inside the expression after match, or after if let, lives until the end of the whole match, so borrow_mut in an arm panics. Read the value into a local first, as below. A plain if condition does not have this problem: its guards end before the block runs.
Cycles through parent links. A tree whose nodes hold Rc handles to their parent, or a list whose items point back to the list, is never freed. Make the link toward the owner a Weak. To find such a leak, print Rc::strong_count in a test, or give the type a Drop that logs.
Rc<RefCell<T>> to silence the compiler. Wrapping everything in Rc<RefCell<T>> makes most borrow errors go away, and turns each one into a possible panic. Prefer one clear owner and &mut parameters, and keep each RefCell borrow short.
An Rc sent to a thread. E0277 says Rc cannot be sent between threads safely. Use Arc, and Mutex in place of RefCell.
let beds = RefCell::new(vec![0, 2, 1]);
// the guard ends at this semicolon
let room = beds.borrow().iter().position(|&n| n > 0);
match room {
Some(i) => beds.borrow_mut()[i] -= 1,
None => println!("hostel full"),
} Keep RefCell guards short, never inside a match scrutinee; point back toward an owner with Weak; and do not use Rc<RefCell<T>> in place of a clear owner.
Experiment
Find the borrow that lasts for a whole match
A guard made inside the expression after match is a temporary value, and Rust keeps it until the end of the whole match, so it is still alive in every arm. In a let statement, the temporary is dropped at the semicolon.
Why it matters Read what you need from a RefCell into a local first, then match on the local.
Predict what prints. Then replace room in the line match room { with beds.borrow().iter().position(|&n| n > 0), and predict what happens.
use std::cell::RefCell;
fn main() {
let beds = RefCell::new(vec![0, 2, 1]);
// the first room that still has a free bed
let room = beds.borrow().iter().position(|&n| n > 0);
match room {
Some(i) => beds.borrow_mut()[i] -= 1,
None => println!("hostel full"),
}
println!("{:?}", beds.borrow());
}
What to look for
Watch the borrow counter of beds, the number after borrow=. On line 6 it shows borrow=1 while position runs; the stepper stops in a frame named main::main::{closure#0}, the closure. At line 7 the counter is 0 again, and room shows Some(1). The run prints [0, 1, 1]. After the edit, the counter still shows borrow=1 when the stepper reaches the Some arm, and the run ends with thread 'main' panicked at 'already borrowed: BorrowMutError', main.rs:8:25.
Expected output
[0, 1, 1]07 / Try it
Can you count the owners and predict the output?
Count the owners of the ticket list, then decide what the program prints.
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let tickets = Rc::new(RefCell::new(vec!["ferry"]));
let ana = Rc::clone(&tickets);
let note = Rc::downgrade(&tickets);
ana.borrow_mut().push("bus");
drop(tickets);
println!("{}", Rc::strong_count(&ana));
match note.upgrade() {
Some(list) => println!("{:?}", list.borrow()),
None => println!("gone"),
}
}
What does the program print? What would it print if the second line of main were let ana = Rc::new(RefCell::new(tickets.borrow().clone()));?
Reveal the reasoning
It prints 1, then ["ferry", "bus"]. tickets and ana are two handles to one RefCell, so the strong count is 2, and note adds to the weak count only. ana’s write guard ends at the end of its statement, so the push is seen through every handle. drop(tickets) leaves ana as the only owner: 1. The list still exists, so upgrade returns Some. With the changed line, ana owns a new RefCell with a copy of the Vec. drop(tickets) then drops the original list, whose only owner was tickets, so the program prints 1, then gone: note pointed to the original, not to ana’s copy, and ana’s push went into the copy.
Practice tickets
- RUST-901 · Packing a new item closes the app → Free
- RUST-902 · Each phone shows a different trip fund → Sign in · Pro
- RUST-903 · Keycards handed back still open their rooms → Sign in · Pro
Keep exploring
Primary references
- The Rust Programming Language: Smart Pointers ↗
- The Rust Programming Language: Shared-State Concurrency ↗
- Rust standard library: Box ↗
- Rust standard library: std::rc (Rc and Weak) ↗
- Rust standard library: std::cell (Cell and RefCell) ↗
- Rust standard library: std::sync::Arc ↗
- Rust standard library: std::sync::Mutex ↗
- The Rust Reference: Temporary scopes ↗
- Rust error codes index ↗