Step 1 of 6 · Reading · ~4 min
Learn
Smart Pointers and Memory
Box, Rc and RefCell
After this lesson you can pick the pointer that fits an ownership shape: Box for a type that contains itself, Rc for several owners of one value, and Rc<RefCell<T>> for a value several owners may change. You will also know which compiler checks these pointers move to run time.
Until now every value had one owner plus & and &mut borrows checked at compile time (Ownership and Borrowing, Borrowing). Here the value contains itself, has several owners, or changes through a shared handle.
Box: a type that contains itself
A Box<T> puts a T on the heap and owns it. The box is one pointer, so its size is fixed, which is what lets an enum hold its own type:
enum Expr { Num(i32), Add(Expr, Expr) }
error[E0072]: recursive type `Expr` has infinite size
Add would contain two Exprs, each able to contain two more: no finite size. Put the recursive parts behind a Box:
enum Expr { Num(i32), Add(Box<Expr>, Box<Expr>) }
use Expr::{Add, Num};
fn eval(e: &Expr) -> i32 {
match e { Num(n) => *n, Add(a, b) => eval(a) + eval(b) }
}
fn main() {
let e = Add(Box::new(Num(2)), Box::new(Add(Box::new(Num(3)), Box::new(Num(4)))));
println!("{}", eval(&e)); // 9
}
A Box has one owner like any value; dropping it frees everything beneath it.
Rc: several owners of one value
Rc<T> lets several handles own one heap value. Rc::clone(&x) copies no data: it makes a new handle and adds 1 to the count Rc::strong_count reports. The value is freed when the last handle is dropped.
use std::rc::Rc;
fn main() {
let theme = Rc::new(String::from("dark"));
let editor = Rc::clone(&theme);
println!("{}", Rc::strong_count(&theme)); // 2
{
let preview = Rc::clone(&theme);
println!("{} {}", Rc::strong_count(&theme), preview); // 3 dark
} // preview dropped
drop(editor);
println!("{}", Rc::strong_count(&theme)); // 1
}
Handles are read-only: let n = Rc::new(5); *n += 1; fails with
error[E0594]: cannot assign to data in an `Rc`
To ask whether two handles share one value, use Rc::ptr_eq. The == operator compares contents:
let a = Rc::new(String::from("x"));
let b = Rc::clone(&a);
let c = Rc::new(String::from("x"));
println!("{} {}", a == b, a == c); // true true
println!("{} {}", Rc::ptr_eq(&a, &b), Rc::ptr_eq(&a, &c)); // true false
Arc is Rc with an atomic count, for threads (the Threads lesson).
RefCell: borrow rules checked at run time
RefCell<T> allows mutation through a shared handle. borrow() and borrow_mut() return guards, and the cell counts live guards: any number of readers or one writer, never both. A guard is released when it goes out of scope.
use std::cell::RefCell;
let steps = RefCell::new(Vec::new()); // not `mut`
steps.borrow_mut().push("start");
steps.borrow_mut().push("run"); // the first guard is already gone
println!("{:?} len {}", steps.borrow(), steps.borrow().len()); // ["start", "run"] len 2
Break the rule and the code still compiles; it panics:
let first = steps.borrow(); // reader guard still alive
steps.borrow_mut().push("late"); // writer requested
thread 'main' panicked at 'already borrowed: BorrowMutError'
Rc<RefCell<T>>: shared and changeable
Combine them when several owners must see each other's changes on one thread. A db logger and a web logger that each hold Rc::clone(&log) of an Rc<RefCell<Vec<String>>> write into one list, and strong_count is 3 with the original.
Two handles to one cell are still one cell. let x = a.borrow_mut(); let y = b.borrow_mut(); panics when b is a clone of a, though the names differ. Compare with Rc::ptr_eq before taking both.
Common mistakes
- A second
Rc::new(...)instead ofRc::clone. You get two independent values: a change through one name never shows in the other, and the count stays 1. - Two live
borrow_mut()guards on one cell. Two names for one value panic withalready borrowedand all later output is lost. - Comparing names or contents to detect the same value. Different names can share one value, and equal contents do not mean shared; use
Rc::ptr_eq. - Miscounting after a removal.
HashMap::removereturns the handle it took out, and that handle still counts until it is dropped.
Your exercise
Manage bank accounts where several names can refer to one shared account. The starter parses commands and handles open; implement the rest, and report a transfer between two names of the same account instead of panicking.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…