Smart Pointers
Box for data on the heap, Rc and Arc for shared ownership through reference counting, Weak to break cycles, and RefCell and Cell for interior mutability checked at run time.
What is a smart pointer?
A reference (&T) only borrows: it points to a value
that somebody else owns. A
smart pointer is a value that
behaves like a pointer and also owns what it points to, often with
extra bookkeeping, and cleans up when it goes out of scope. You have
used two already: a String owns its bytes on the heap,
a Vec<T> owns its elements. What makes a type a
smart pointer is two standard traits. Deref lets
*p and method calls reach the value inside, and
Drop runs the cleanup code when the owner goes away
(Drop is on Ownership).
The general ideas behind this page, the heap and reference counting, are on Memory Management. The standard library's smart pointers each answer one question:
| Type | Answers | Checked |
|---|---|---|
Box<T> |
"I need this value on the heap, with one owner." | at compile time, like any owned value |
Rc<T> |
"Several parts of one thread own this value together." | counter at run time; read-only |
Arc<T> |
"Several threads own this value together." | atomic counter at run time; read-only |
Weak<T> |
"I want to refer to it without keeping it alive." |
at run time: upgrade() may return
None
|
RefCell<T>, Cell<T>
|
"I need to change this through a shared reference." | borrowing rules at run time (one thread only) |
Box: one value on the heap
Box<T> is the simplest smart
pointer: Box::new(value) moves the value to the heap
and gives you a pointer to it, which is the box's only owner. When
the box goes out of scope, the value is dropped and the heap memory
freed. Apart from where the value lives, a box behaves like the
value: it prints the same, compares the same through *,
and you call the value's methods on it directly.
fn main() {
let on_stack: u64 = 412_338; // Wh, a meter total
let on_heap: Box<u64> = Box::new(412_338);
println!("{on_stack} {on_heap}"); // a Box prints like its contents
println!("equal: {}", on_stack == *on_heap); // * reaches the value inside
let doubled = *on_heap * 2;
println!("doubled {doubled}");
} // on_heap goes out of scope: the Box frees its heap memory here
412338 412338 equal: true doubled 824676
For a single number, a box is pointless; values on the stack are faster. A box earns its place in three situations: a type that contains itself, a value whose exact type is only known at run time (a trait object), and a large value you want to move around without copying all of its bytes.
Recursive types
The compiler must know the size of every type. A type that contains a value of itself directly would be infinitely large, so the compiler refuses it:
// A price rule that can contain other price rules (example values, in cents)
enum Rule {
Fixed(u64),
Plus(Rule, Rule),
}
fn main() {
let _r = Rule::Fixed(9450);
}
example.rs:2:1: error[E0072]: recursive type `Rule` has infinite size
$ rustc --edition 2024 example.rs
error[E0072]: recursive type `Rule` has infinite size
--> example.rs:2:1
|
2 | enum Rule {
| ^^^^^^^^^
3 | Fixed(u64),
4 | Plus(Rule, Rule),
| ---- recursive without indirection
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
4 | Plus(Box<Rule>, Rule),
| ++++ +
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0072`.
A box has a fixed size, one pointer, whatever is inside, so it breaks the cycle. Here is a small price rule in which a rule can contain other rules, the shape of an expression tree. The €0.2700 per kWh is Kestrel's fixed tariff (350 kWh = €94.50); the monthly fee and the discount are example values:
// A price rule that can contain other price rules (amounts in cents)
enum Rule {
Fixed(u64),
Plus(Box<Rule>, Box<Rule>),
Discount { percent: u64, of: Box<Rule> },
}
fn cents(rule: &Rule) -> u64 {
match rule {
Rule::Fixed(c) => *c,
Rule::Plus(a, b) => cents(a) + cents(b),
Rule::Discount { percent, of } => cents(of) * (100 - percent) / 100,
}
}
fn main() {
// 350 kWh at €0.2700 plus a monthly fee, with a 10% discount on the total
let bill = Rule::Discount {
percent: 10,
of: Box::new(Rule::Plus(
Box::new(Rule::Fixed(9450)),
Box::new(Rule::Fixed(550)),
)),
};
let c = cents(&bill);
println!("€{}.{:02}", c / 100, c % 100);
}
€90.00
cents(a) receives a
&Box<Rule> where a &Rule is
expected and the compiler converts it automatically. That conversion
is the subject of the next section.
How big is a smart pointer?
On a 64-bit Linux machine (ARM64), where this page's examples were run, a pointer is 8 bytes. Sizes are platform-specific: a 32-bit target has 4-byte pointers.
use std::cell::RefCell;
use std::mem::size_of;
use std::rc::Rc;
use std::sync::Arc;
fn show<T>(name: &str) {
println!("{name:<17}{:>3} bytes", size_of::<T>());
}
fn main() {
show::<u64>("u64");
show::<Box<u64>>("Box<u64>");
show::<Option<Box<u64>>>("Option<Box<u64>>");
show::<Rc<u64>>("Rc<u64>");
show::<Arc<u64>>("Arc<u64>");
show::<RefCell<u32>>("RefCell<u32>");
show::<String>("String");
show::<Box<dyn Fn()>>("Box<dyn Fn()>");
}
u64 8 bytes Box<u64> 8 bytes Option<Box<u64>> 8 bytes Rc<u64> 8 bytes Arc<u64> 8 bytes RefCell<u32> 16 bytes String 24 bytes Box<dyn Fn()> 16 bytes
Box, Rc and Arc are a single
pointer; the counters of Rc and Arc live
on the heap next to the value.
Option<Box<u64>> is still 8 bytes, because
a box can never be null, so the compiler uses the null value to mean
None. RefCell<u32> is larger than
the u32 because it stores its borrow state next to the
value. Box<dyn Fn()> is a "fat" pointer: the
address of the closure plus the address of a table with its methods
(see trait objects on
Traits and Generics).
Deref and deref coercion
The * operator follows a reference to its value. A
smart pointer implements the Deref trait, so
*boxed reaches the value inside a box the same way.
Rust also applies Deref by itself in two places. A
method call such as boxed.len() follows pointers until
it finds the method. And
deref coercion converts a
reference to a smart pointer into a reference to what it points to,
whenever a function expects that: a &String becomes
a &str, and a
&Box<String> becomes a
&String and then a &str.
use std::rc::Rc;
fn shout(text: &str) -> String {
text.to_uppercase()
}
fn main() {
let literal: &str = "m-30004";
let owned: String = String::from("m-30004");
let boxed: Box<String> = Box::new(String::from("m-30004"));
let shared: Rc<String> = Rc::new(String::from("m-30004"));
// shout wants a &str; each of these turns into one automatically
println!("{}", shout(literal));
println!("{}", shout(&owned)); // &String -> &str
println!("{}", shout(&boxed)); // &Box<String> -> &String -> &str
println!("{}", shout(&shared)); // &Rc<String> -> &String -> &str
// methods work through the pointer too
println!("{} {} {}", owned.len(), boxed.len(), shared.len());
}
M-30004 M-30004 M-30004 M-30004 7 7 7
This is why functions should take &str rather than
&String, and &[T] rather than
&Vec<T>: callers can then pass a literal, a
String, a boxed string or a shared one without
converting anything. Deref coercion happens only for references,
only at compile time, and costs nothing at run time. A
DerefMut trait does the same for &mut;
Rc and Arc do not implement it, as the
next section shows.
Rc: shared ownership in one thread
Ownership normally has one owner. Sometimes a value really has
several: one tariff table used by many contracts, a configuration
shared by several parts of a program, a node in a graph that several
other nodes point to. No single one of them should free it; it
should be freed when the last one is done with it.
Rc<T> (reference counted) does
exactly that. Rc::new puts the value on the heap next
to a counter, Rc::clone(&rc) makes another owning
handle and adds one to the count, and every handle that is dropped
subtracts one. When the count reaches zero, the value is dropped.
use std::rc::Rc;
struct Tariff {
name: String,
price: u64, // ten-thousandths of a euro per kWh
}
struct Contract {
number: u32,
tariff: Rc<Tariff>,
}
fn main() {
let fixed = Rc::new(Tariff { name: String::from("fixed"), price: 2700 });
println!("count after new: {}", Rc::strong_count(&fixed));
let a = Contract { number: 1, tariff: Rc::clone(&fixed) };
let b = Contract { number: 2, tariff: Rc::clone(&fixed) };
println!("count with two contracts: {}", Rc::strong_count(&fixed));
for c in [&a, &b] {
println!("contract {} pays {} at {}", c.number, c.tariff.name, c.tariff.price);
}
drop(a);
println!("count after dropping one: {}", Rc::strong_count(&fixed));
println!("same tariff: {}", Rc::ptr_eq(&fixed, &b.tariff));
}
count after new: 1 count with two contracts: 3 contract 1 pays fixed at 2700 contract 2 pays fixed at 2700 count after dropping one: 2 same tariff: true
The count is 3 with two contracts because fixed itself
is a handle too. Rc::clone copies only the pointer and
increases the counter; the tariff is not copied, which
Rc::ptr_eq confirms. (Writing
fixed.clone() does the same, but
Rc::clone(&fixed) makes it visible to a reader that
no deep copy happens.)
Box owns its value alone. An Rc
allocation keeps two counters next to the value; every
Rc::clone adds one to the strong count. An
Arc looks the same, with atomic counters.
Because several handles may read the value at the same time,
Rc hands out shared references only. Changing the value
through it is refused at compile time:
use std::rc::Rc;
fn main() {
let price = Rc::new(2700u64);
let other = Rc::clone(&price);
*other += 100;
println!("{price}");
}
example.rs:6:5: error[E0594]: cannot assign to data in an `Rc`: cannot assign
The full message adds: "trait DerefMut is required to
modify through a dereference, but it is not implemented for
Rc<u64>". When shared data really must change,
combine Rc with a RefCell (below).
Weak references and reference cycles
Reference counting has one weakness, in every language that uses it:
two values that hold strong references to each other keep each
other's count above zero forever, even when nothing else refers to
either of them. Rust does not stop you from building such a cycle.
The memory leaks, and the Drop code never runs:
use std::cell::RefCell;
use std::rc::Rc;
struct Meter {
id: &'static str,
next: RefCell<Option<Rc<Meter>>>, // a link to another meter
}
impl Drop for Meter {
fn drop(&mut self) {
println!("freed {}", self.id);
}
}
fn main() {
{
let a = Rc::new(Meter { id: "M-1", next: RefCell::new(None) });
let b = Rc::new(Meter { id: "M-2", next: RefCell::new(None) });
*b.next.borrow_mut() = Some(Rc::clone(&a)); // b -> a
println!("a count {}, b count {}", Rc::strong_count(&a), Rc::strong_count(&b));
}
println!("end of first block");
{
let a = Rc::new(Meter { id: "M-3", next: RefCell::new(None) });
let b = Rc::new(Meter { id: "M-4", next: RefCell::new(None) });
*b.next.borrow_mut() = Some(Rc::clone(&a)); // b -> a
*a.next.borrow_mut() = Some(Rc::clone(&b)); // a -> b: a cycle
println!("a count {}, b count {}", Rc::strong_count(&a), Rc::strong_count(&b));
}
println!("end of second block (M-3 and M-4 were never freed)");
}
a count 2, b count 1 freed M-2 freed M-1 end of first block a count 2, b count 2 end of second block (M-3 and M-4 were never freed)
In the first block, b goes out of scope first and is
freed, which drops its link to a and lowers
a's count to 1; when a goes out of scope
right after, its count reaches 0 and it is freed too. In the second
block each meter keeps the other's count at 1 after both variables
are gone. The Rust Reference lists
memory leaks
among the behavior that is "not considered unsafe": a leak wastes
memory but cannot corrupt it. It is still a bug, in a long-running
agent or service especially.
The fix is to decide which direction owns. A
weak reference,
Weak<T>, made with
Rc::downgrade(&rc), points to the same allocation
but adds to the separate weak count, which does not keep the value
alive. Before you use it, upgrade() turns it back into
an Rc: Some(rc) while the value exists,
None after it was dropped. The usual shape is a tree:
parents own their children with Rc, children point back
to their parent with Weak.
use std::cell::RefCell;
use std::rc::{Rc, Weak};
struct Home {
id: &'static str,
meters: RefCell<Vec<Rc<Meter>>>, // the home owns its meters
}
struct Meter {
id: &'static str,
home: Weak<Home>, // a meter only refers back to its home
}
fn main() {
let home = Rc::new(Home { id: "h0042", meters: RefCell::new(Vec::new()) });
let meter = Rc::new(Meter { id: "M-1", home: Rc::downgrade(&home) });
home.meters.borrow_mut().push(Rc::clone(&meter));
println!("strong {}, weak {}", Rc::strong_count(&home), Rc::weak_count(&home));
println!("{} has {} meter(s)", home.id, home.meters.borrow().len());
match meter.home.upgrade() {
Some(h) => println!("{} belongs to {}", meter.id, h.id),
None => println!("{} has no home any more", meter.id),
}
drop(home); // the last strong reference: Home is freed (and its Vec with it)
match meter.home.upgrade() {
Some(h) => println!("{} belongs to {}", meter.id, h.id),
None => println!("{} has no home any more", meter.id),
}
}
strong 1, weak 1 h0042 has 1 meter(s) M-1 belongs to h0042 M-1 has no home any more
The home owns the meter (strong), the meter only knows its home
(weak). Dropping the last strong handle to the home frees it even
though the meter still points back, and the meter can tell:
upgrade() returns None instead of a
dangling pointer.
Interior mutability: RefCell and Cell
The borrowing rules say: many shared references or one mutable
reference, never both (see
Borrowing and References). The compiler checks them on the source code, and sometimes the
source code does not show enough: a value shared through
Rc, or a method that takes &self but
needs to update a cache or a log.
Interior mutability is
the pattern for these cases: a type that allows changing its
contents through a shared reference, and that enforces safety some
other way.
RefCell: borrowing checked at run time
RefCell<T> moves the borrow
check from compile time to run time. borrow() returns a
guard that acts like &T,
borrow_mut() one that acts like
&mut T, and the RefCell counts the
guards that are alive. The rules are the same as at compile time;
only the moment of checking differs. In this example, a developer on
Kestrel's platform team writes a Rust prototype of the parser from
BUG-1060, to compare with the manufacturer's C fix. It keeps a log of the
CRC fields it rejected, through &self:
use std::cell::RefCell;
struct Parser {
rejected: RefCell<Vec<String>>, // a log the parser may add to through &self
}
impl Parser {
// &self, not &mut self: callers can share the parser
fn sent_crc(&self, tail: &str) -> Option<u16> {
let digits = tail.strip_suffix("\r\n").unwrap_or(tail);
let ok = !digits.is_empty()
&& digits.len() <= 4
&& digits.bytes().all(|b| b.is_ascii_hexdigit());
if !ok {
self.rejected.borrow_mut().push(digits.to_string());
return None;
}
u16::from_str_radix(digits, 16).ok()
}
}
fn main() {
let parser = Parser { rejected: RefCell::new(Vec::new()) };
for tail in ["F1F4\r\n", "B9F\r\n", "+B9F\r\n", "F1F4F\r\n"] {
println!("{:?} -> {:04X?}", tail.trim_end(), parser.sent_crc(tail));
}
println!("rejected: {:?}", parser.rejected.borrow());
}
"F1F4" -> Some(F1F4) "B9F" -> Some(0B9F) "+B9F" -> None "F1F4F" -> None rejected: ["+B9F", "F1F4F"]
The three-digit CRC B9F that a meter really sent
(BUG-1060) is accepted as 0B9F; +B9F and the five-digit
value are rejected. The explicit digit check matters:
u16::from_str_radix alone accepts a leading
+, so the type system does not replace validation. The
guard from borrow_mut() lives only until the end of the
push statement. Keep a guard alive while asking for a
conflicting one, and the program stops:
use std::cell::RefCell;
fn main() {
let log = RefCell::new(vec![350]);
let first = log.borrow(); // a shared borrow, checked at run time
println!("first {}", first[0]);
log.borrow_mut().push(412); // a mutable borrow while `first` still lives
println!("{}", first.len());
}
first 350 thread 'main' (3199224) panicked at example.rs:8:9: RefCell already borrowed
Where the compiler would have refused the program, a
RefCell panics at run time. The cost of the flexibility
is that a mistake shows up while the program runs, on the path that
triggers it, and not when you build it. Two habits keep that rare:
keep guards short-lived (inside a small block, or in a single
expression), and use try_borrow() and
try_borrow_mut(), which return a Result
instead of panicking, where a conflict is possible:
use std::cell::RefCell;
fn main() {
let log = RefCell::new(vec![350]);
{
let first = log.borrow();
println!("first {}", first[0]);
println!("try_borrow_mut while reading: {:?}", log.try_borrow_mut().is_ok());
} // `first` ends here, and with it the shared borrow
log.borrow_mut().push(412);
println!("after: {:?}", log.borrow());
}
first 350 try_borrow_mut while reading: false after: [350, 412]
Cell: for small Copy values
Cell<T> is the simpler sibling. It never hands
out references to its contents at all: get() returns a
copy, set() replaces the value. Without references
there is nothing to check, so Cell has no run-time
borrow state and cannot panic. It suits counters and flags:
get() needs a Copy type such as a number
or a bool.
use std::cell::Cell;
struct Meter {
id: &'static str,
reads: Cell<u32>, // how often this meter was read
}
impl Meter {
fn read(&self) -> u32 {
self.reads.set(self.reads.get() + 1);
1234 // watts
}
}
fn main() {
let meter = Meter { id: "M-1", reads: Cell::new(0) };
let total: u32 = (0..3).map(|_| meter.read()).sum();
println!("{} read {} times, total {total} W", meter.id, meter.reads.get());
}
M-1 read 3 times, total 3702 W
Rc and RefCell together
Rc gives several owners, RefCell gives
changes through shared references; together,
Rc<RefCell<T>> is a value that several
parts of one thread own and change. In this example, two parts of a
program add to one shared list of alerts:
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
// one shared, changeable list of alerts
let alerts = Rc::new(RefCell::new(Vec::new()));
let from_parser = Rc::clone(&alerts);
let from_broker = Rc::clone(&alerts);
from_parser.borrow_mut().push("parser: CRC mismatch");
from_broker.borrow_mut().push("broker: reconnected");
println!("{} handles, {} alerts", Rc::strong_count(&alerts), alerts.borrow().len());
for a in alerts.borrow().iter() {
println!("{a}");
}
}
3 handles, 2 alerts parser: CRC mismatch broker: reconnected
Arc: shared ownership across threads
Rc updates its counter with ordinary instructions. If
two threads cloned or dropped the same Rc at the same
moment, the counter could go wrong and the value could be freed
while still in use. Rust does not let that happen: a type that is
not safe to move to another thread does not implement the marker
trait Send, and thread::spawn
requires it. The mistake is caught when you compile:
use std::rc::Rc;
use std::thread;
fn main() {
let tariff = Rc::new(String::from("fixed"));
let copy = Rc::clone(&tariff);
let worker = thread::spawn(move || println!("worker sees {copy}"));
worker.join().unwrap();
println!("main sees {tariff}");
}
example.rs:7:32: error[E0277]: `Rc<String>` cannot be sent between threads safely: `Rc<String>` cannot be sent between threads safely
Arc<T> (atomically reference
counted) is the thread-safe version. It has the same interface as
Rc, but updates the counters with atomic CPU
instructions, which are slightly slower; that is why both exist.
Each thread gets its own handle through Arc::clone:
use std::sync::Arc;
use std::thread;
fn main() {
let tariff = Arc::new(String::from("fixed"));
let workers: Vec<_> = (1..=3)
.map(|n| {
let tariff = Arc::clone(&tariff); // one handle per thread
thread::spawn(move || format!("worker {n} sees {tariff}"))
})
.collect();
for w in workers {
println!("{}", w.join().unwrap()); // joined, and printed, in order
}
println!("handles left: {}", Arc::strong_count(&tariff));
}
worker 1 sees fixed worker 2 sees fixed worker 3 sees fixed handles left: 1
The threads only read the tariff, and that is all an
Arc allows on its own. For data that threads change,
the thread-safe counterpart of RefCell is a
Mutex or RwLock, usually as
Arc<Mutex<T>>: see
Concurrency, which also explains
Send and Sync.
Choosing a smart pointer
| You need | One thread | Several threads |
|---|---|---|
| one owner, value on the heap | Box<T> |
Box<T> (moved to the thread) |
| several owners, read-only | Rc<T> |
Arc<T> |
| a link that must not keep the value alive | rc::Weak<T> |
sync::Weak<T> |
| change through a shared reference |
RefCell<T>, Cell<T>
|
Mutex<T>, RwLock<T>,
atomics
|
| several owners that change the value | Rc<RefCell<T>> |
Arc<Mutex<T>> |
Start with plain ownership and references, and add a smart pointer when the compiler shows you why you need one. Each step down the table moves a check from compile time to run time, or adds a small cost.