# Smart pointers

> Choose between Box, Rc, Arc, Cell, RefCell, Mutex and Weak for heap allocation, shared ownership and interior mutability.

Rust roadmap · Systems & production · 18 min read · https://levelcode.org/roadmaps/rust/smart-pointers

Ownership says every value has one owner, and borrowing says you can have one writer or many readers. Most code fits those rules. Some doesn't: a recursive data structure, a graph where several nodes point at the same node, a cache that many parts of a program read and occasionally update. **Smart pointers** are types that own data and add a capability: heap allocation, shared ownership, or mutation through a shared reference, with the checks moved from compile time to runtime where necessary. This lesson covers `Box`, `Rc` and `Arc`, interior mutability with `Cell`, `RefCell` and `Mutex`, reference cycles and `Weak`, and how to pick one.

## Box: a value on the heap

`Box<T>` puts a value on the heap and owns it. When the box is dropped, so is the value. It has one owner, like any other value, and adds no runtime overhead beyond the allocation.

You need it in three situations.

**Recursive types.** A type can't contain itself directly, because its size would be infinite. A box has a fixed size (one pointer), which breaks the cycle:

```rust
#[derive(Debug)]
enum Expr {
    Num(f64),
    Add(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
}

fn eval(e: &Expr) -> f64 {
    match e {
        Expr::Num(n) => *n,
        Expr::Add(a, b) => eval(a) + eval(b),
        Expr::Mul(a, b) => eval(a) * eval(b),
    }
}

fn main() {
    // (2 + 3) * 4
    let e = Expr::Mul(
        Box::new(Expr::Add(Box::new(Expr::Num(2.0)), Box::new(Expr::Num(3.0)))),
        Box::new(Expr::Num(4.0)),
    );
    println!("{}", eval(&e)); // 20
}
```

**Trait objects.** `Box<dyn Trait>` owns a value whose concrete type is only known at runtime, as in the [traits lesson](https://levelcode.org/roadmaps/rust/traits-and-generics). `Box<dyn std::error::Error>` is the common one.

**Large values you want to move cheaply.** Moving a `Box` copies a pointer, not the whole value. Useful for big arrays or structs that get passed around a lot.

`Box` implements `Deref`, so you can call the inner value's methods directly, and `*b` gives you the value.

## Rc: shared ownership on one thread

Sometimes a value genuinely has several owners, and none of them is clearly the last to need it. `Rc<T>` (reference counted) allows that. Cloning an `Rc` doesn't copy the data; it increments a counter and returns another pointer to the same allocation. When the last `Rc` is dropped, the count hits zero and the value is dropped.

```rust
use std::rc::Rc;

#[derive(Debug)]
struct Config {
    name: String,
}

struct Service {
    config: Rc<Config>,
}

fn main() {
    let config = Rc::new(Config { name: "prod".into() });
    let api = Service { config: Rc::clone(&config) };
    let worker = Service { config: Rc::clone(&config) };

    println!("{}", Rc::strong_count(&config)); // 3
    println!("{} {}", api.config.name, worker.config.name);
    drop(api);
    println!("{}", Rc::strong_count(&config)); // 2
}
```

Writing `Rc::clone(&config)` instead of `config.clone()` is a convention: it signals a cheap pointer copy, not a deep clone.

`Rc` only gives you **shared** references to the value. Shared ownership plus mutation would break the aliasing rule, so `Rc<T>` alone is read-only. `Rc` is also not thread-safe: its counter isn't atomic, so the compiler refuses to send it to another thread.

## Arc: shared ownership across threads

`Arc<T>` (atomically reference counted) is the thread-safe version. The counter uses atomic operations, which cost a little more than `Rc`'s plain increments, so use `Rc` on a single thread and `Arc` when the value crosses threads or async tasks.

```rust
use std::sync::Arc;
use std::thread;

fn main() {
    let words = Arc::new(vec!["alpha", "beta", "gamma"]);
    let mut handles = Vec::new();
    for i in 0..3 {
        let words = Arc::clone(&words); // each thread gets its own pointer
        handles.push(thread::spawn(move || words[i].len()));
    }
    let total: usize = handles.into_iter().map(|h| h.join().unwrap()).sum();
    println!("{total}"); // 14
}
```

`Arc<T>` is how you share read-only state in servers: configuration, a connection pool, a compiled template set. Like `Rc`, it only hands out shared references.

## Interior mutability

What if shared owners need to change the value? **Interior mutability** types let you mutate data through a shared reference, `&T`. They don't break the aliasing rule; they enforce it in a different way.

| Type | Enforces the rule… | Thread-safe | Cost |
| --- | --- | --- | --- |
| `Cell<T>` | By never handing out references: you `get` a copy or `set` a new value | No | None |
| `RefCell<T>` | At runtime: tracks active borrows, **panics** on a conflict | No | A counter check per borrow |
| `Mutex<T>` | At runtime: one locker at a time, others **block** | Yes | A lock per access |
| `RwLock<T>` | At runtime: many readers or one writer | Yes | A lock per access |
| Atomics (`AtomicU64`…) | In hardware, for single integers and flags | Yes | An atomic instruction |

`Cell` is for small `Copy` values like counters and flags:

```rust
use std::cell::Cell;

struct Page {
    views: Cell<u32>,
}

impl Page {
    fn view(&self) {               // &self, not &mut self
        self.views.set(self.views.get() + 1);
    }
}

fn main() {
    let p = Page { views: Cell::new(0) };
    p.view();
    p.view();
    println!("{}", p.views.get()); // 2
}
```

`RefCell` lends out real references, checked at runtime. `borrow()` returns a guard that acts like `&T`, and `borrow_mut()` one that acts like `&mut T`. The borrow lasts until the guard is dropped.

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

fn main() {
    let log = Rc::new(RefCell::new(Vec::<String>::new()));

    let writer = Rc::clone(&log);
    let record = move |msg: &str| writer.borrow_mut().push(msg.to_string());

    record("started");
    record("finished");
    println!("{:?}", log.borrow()); // ["started", "finished"]

    let _reader = log.borrow();
    // log.borrow_mut(); // would panic: already borrowed
}
```

`Rc<RefCell<T>>` is the single-threaded idiom for "shared and mutable". Its multithreaded counterpart is `Arc<Mutex<T>>`, covered in the [concurrency lesson](https://levelcode.org/roadmaps/rust/concurrency). See [interior mutability](https://levelcode.org/concepts/interior-mutability) for a compact summary.

> [!WARNING]
> `RefCell` turns a compile-time error into a runtime panic. Keep its borrows short (borrow, use, drop in one expression), and never hold a `borrow_mut()` guard while calling code that might borrow the same cell again. If you find `RefCell` everywhere, the design is probably fighting ownership; consider restructuring so one owner holds the data and others send it messages or receive `&mut` when they need it.

## Cycles and Weak

Reference counting can't free a cycle: if A holds an `Rc` to B and B holds one to A, neither count reaches zero, and both leak. Rust's safety guarantees don't cover leaks (they're memory-safe, just wasteful), so this is your job.

The tool is `Weak<T>`, a non-owning pointer you get from `Rc::downgrade`. It doesn't keep the value alive; to use it, call `upgrade()`, which returns `Some(Rc<T>)` if the value still exists and `None` if it's gone. The usual pattern is that owners point *down* with `Rc` and children point *up* with `Weak`:

```rust
use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    name: String,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let root = Rc::new(Node { name: "root".into(), parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]) });
    let leaf = Rc::new(Node { name: "leaf".into(), parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]) });

    *leaf.parent.borrow_mut() = Rc::downgrade(&root);
    root.children.borrow_mut().push(Rc::clone(&leaf));

    let parent = leaf.parent.borrow().upgrade();
    println!("{:?}", parent.map(|p| p.name.clone())); // Some("root")
    println!("{} {}", Rc::strong_count(&root), Rc::weak_count(&root)); // 1 1
}
```

Graphs with arbitrary edges are often simpler without pointers at all: store nodes in a `Vec` and refer to them by index (an **arena**). That sidesteps `Rc`, `RefCell` and cycles, and it's what many compilers and game engines do.

## Choosing a smart pointer

Ask these questions in order:

1. **Does the value need to be on the heap at all?** Recursive type, trait object or very large value: `Box<T>`. Otherwise, a plain value.
2. **Does it have more than one owner?** One thread: `Rc<T>`. Several threads or async tasks: `Arc<T>`.
3. **Do shared owners need to mutate it?** Single thread: `Cell` for `Copy` values, `RefCell` otherwise. Several threads: atomics for counters, `Mutex` or `RwLock` otherwise.
4. **Could ownership form a cycle?** Make the back-edges `Weak`, or switch to indexes into an arena.

So the common combinations are `Box<dyn Trait>`, `Rc<RefCell<T>>` on one thread, and `Arc<Mutex<T>>` or `Arc<RwLock<T>>` across threads.

## Practice

Build a tiny in-memory "bank" where several account holders share accounts.

1. Define `struct Account { owner: String, balance: RefCell<i64> }` and share accounts with `Rc<Account>`.
2. Write `fn transfer(from: &Account, to: &Account, amount: i64) -> Result<(), String>` that refuses overdrafts. It takes shared references only.
3. Create two accounts, give a joint holder `Rc` clones of both, and perform transfers from each holder. Print the strong counts.
4. What happens if you call `transfer(&a, &a, 10)` with the same account, and your implementation holds both `borrow_mut()` guards at once? Fix it so self-transfers work (or are rejected cleanly).

**Solution**

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

struct Account {
    owner: String,
    balance: RefCell<i64>,
}

fn transfer(from: &Account, to: &Account, amount: i64) -> Result<(), String> {
    if std::ptr::eq(from, to) {
        return Err("can't transfer to the same account".into());
    }
    let mut src = from.balance.borrow_mut();
    if *src < amount {
        return Err(format!("{} has insufficient funds", from.owner));
    }
    let mut dst = to.balance.borrow_mut();
    *src -= amount;
    *dst += amount;
    Ok(())
}

fn main() {
    let a = Rc::new(Account { owner: "ana".into(), balance: RefCell::new(100) });
    let b = Rc::new(Account { owner: "bo".into(), balance: RefCell::new(20) });

    let joint = (Rc::clone(&a), Rc::clone(&b));
    transfer(&a, &b, 30).unwrap();
    transfer(&joint.1, &joint.0, 10).unwrap();
    println!("{:?}", transfer(&b, &a, 1_000));     // Err("bo has insufficient funds")

    println!("{} {}", a.balance.borrow(), b.balance.borrow()); // 80 40
    println!("{} {}", Rc::strong_count(&a), Rc::strong_count(&b)); // 2 2
    println!("{:?}", transfer(&a, &a, 10));         // Err("can't transfer to the same account")
}
```

Without the `ptr::eq` check, a self-transfer calls `borrow_mut()` twice on the same `RefCell` while the first guard is alive, and the program panics with `already borrowed: BorrowMutError`. That's `RefCell` doing its job: the same code with plain `&mut` references would be a compile error. Checking identity up front turns it into a clean `Err`.

## Key takeaways

- `Box<T>` puts one owned value on the heap: for recursive types, trait objects and cheap moves of large values.
- `Rc<T>` (one thread) and `Arc<T>` (many threads) allow shared ownership; cloning bumps a counter and the value drops with the last owner.
- Shared ownership is read-only. Interior mutability (`Cell`, `RefCell`, `Mutex`, `RwLock`, atomics) moves the aliasing check to runtime.
- `RefCell` panics on conflicting borrows; keep borrows short, and treat widespread `RefCell` as a design smell.
- Reference cycles leak; break them with `Weak`, or replace pointers with indexes into an arena.

## Quiz

1. Why does enum List { Cons(i32, List), Nil } fail to compile, and what fixes it?

   A. Enums can't contain i32; use a struct
   B. It has infinite size; use Box<List> for the recursive field
   C. Recursive enums need Rc; Box doesn't work
   D. Nil must carry a value

   **Answer: B.** A type that directly contains itself has no finite size. Box<List> is one pointer wide, so the enum's size becomes known.

2. You need several threads to read a large, immutable lookup table loaded at startup. What should you use?

   A. Rc<HashMap<…>>
   B. Arc<Mutex<HashMap<…>>>
   C. Arc<HashMap<…>>
   D. RefCell<HashMap<…>>

   **Answer: C.** Arc gives thread-safe shared ownership, and immutable data needs no lock. Rc isn't Send, and a Mutex would serialize readers for no reason.

3. What happens if code calls cell.borrow_mut() while a guard from cell.borrow() is still alive?

   A. The program panics at runtime
   B. It's a compile error
   C. The second call blocks until the first guard is dropped
   D. The reader sees the new value immediately

   **Answer: A.** RefCell checks borrows at runtime and panics on a conflict (try_borrow_mut returns an Err instead). Blocking is what a Mutex does.

4. A tree's children hold Rc pointers to their parent, and the parent holds Rc pointers to its children. What's wrong?

   A. Nothing; Rc handles cycles
   B. It's a data race
   C. It won't compile
   D. It's a reference cycle that is never freed; make the parent pointers Weak

   **Answer: D.** Each node's strong count never reaches zero, so the tree leaks. Owners pointing down with Rc and children pointing up with Weak breaks the cycle.
