Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Every pointer type

&T, &mut T, slices, Box, Rc, Arc, Weak, Cell and RefCell, and raw pointers. What each one owns, who can change the value, and what it costs.

Go down this list and stop at the first one that fits.

  • Borrowing for a while → &T to read, &mut T to change. Zero cost, checked at compile time. Borrowing part of a sequence → a slice, &[T] or &str.
  • One owner, but the value must be on the heap (a recursive type, a large value you move around, a dyn Trait) → Box<T>.
  • Several owners, one thread → Rc<T>. Several threads → Arc<T>. A back-pointer that mustn't keep the value alive (a child pointing at its parent) → Weak<T>.
  • Shared, but it still has to change → put Cell<T> (small Copy values) or RefCell<T> inside an Rc; Mutex<T> or RwLock<T> inside an Arc.
  • Talking to C, or building a data structure the borrow checker can't follow → a raw pointer, *const T / *mut T, inside unsafe, wrapped in a safe API.

The further down you go, the more you pay at run time or the more you have to check yourself.

Borrowed

Section

&T

A shared, read-only borrow.

Section

&mut T

An exclusive borrow that may change the value.

A borrow of part of a sequence, carrying its length.

A pointer that carries a second word of information, like a length or a vtable.

Example

On a 64-bit machine size_of::<&str>() is 16: an address and a length. A &dyn Display is also 16: an address and a vtable pointer.

Owning

Section

Box

One owner, the value on the heap. For recursive types and trait objects.

Section

Deref and Drop

The two traits that make a type behave like a pointer and clean up after itself.

Section

Rc

Counted shared ownership on one thread.

Keeping a count of how many owners share a value and freeing it when the count reaches zero.

Example

let a = Arc::new(cfg); let b = Arc::clone(&a); makes Arc::strong_count(&a) 2; the config is freed when both are dropped.

Rc's count, and Arc's, which is updated atomically so threads can share it.

A pointer to a shared value that doesn't keep the value alive.

Example

A tree's child holds Weak<Node> to its parent; parent.upgrade() gives Some(Rc<Node>) while the parent is still alive.

Changing a shared value

Interior mutability on one thread.

Formal. RefCell<T> allows mutation through a shared reference by tracking borrows at run time: borrow() hands out any number of shared guards and borrow_mut() one exclusive guard, and each guard releases its borrow when dropped. Asking for a borrow that would break the rules (an exclusive one while any other exists, or a shared one while an exclusive one exists) panics ("RefCell already borrowed"); try_borrow/try_borrow_mut return an error instead. RefCell is not Sync, so it is single-threaded only.

In plain English. The same rule as always, many readers or one writer, but checked while the program runs instead of when it compiles. Break it and the program stops with a panic instead of a compile error.

Example. let log = RefCell::new(Vec::new()); let a = log.borrow(); log.borrow_mut().push(1); panics, because a is still reading.

Why this rule exists. Some designs (a graph, a test fake that records calls through &self) need shared data that changes; RefCell keeps them memory-safe when the compiler can't prove the borrows statically.

The borrowing rules, checked when the program runs; break them and it panics.

Section

RwLock

Many readers or one writer, across threads.

Unchecked

Section

Raw pointers

*const T and *mut T: no lifetime, no borrow check, and dereferencing needs unsafe.

Box::into_raw hands ownership to C; Box::from_raw takes it back.

Choosing

The decision above, worked through on real cases.

When a Box or a clone shows up in a profile, and what to do about it.