← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Rust Smart Pointers (Box, Rc, Arc, RefCell)

rust Intermediate
debt(d7/e5/b5/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). Misusing smart pointers often compiles fine — RefCell double-borrows panic at runtime, Rc cycles leak silently, and needless Arc overhead is invisible. detection_hints lists clippy and automated:no, so clippy catches some style cases but the dangerous misuses (borrow panics, cycles) surface only at runtime or in review.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix is conceptually 'pick the right pointer,' but swapping Rc<RefCell> for Arc<Mutex> or restructuring to break a cycle propagates through every site that touches the shared value, since the wrapper type is part of every API signature.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). applies_to spans web/cli/queue/library and the chosen pointer type pervades type signatures of everything that shares the data. A wrong choice (or a load-bearing Rc<RefCell<T>> graph) shapes ongoing work across many call sites without fully defining the system's shape.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The misconception is explicit: developers believe Rc<T> permits mutation and that Arc is just a faster Rc, when Rc/Arc give only shared immutable access and Arc is actually slower due to atomic counters. This contradicts the intuitive mental model and leads competent newcomers to guess wrong.

About DEBT scoring →

Also Known As

Box Rc Arc RefCell rust smart pointers interior mutability reference counting

TL;DR

Smart pointers wrap heap allocations with ownership semantics: Box owns one value, Rc/Arc share ownership, and RefCell adds runtime-checked interior mutability.

Explanation

A smart pointer is a struct that behaves like a reference but carries extra ownership and management logic, usually implementing `Deref` so you can use it like the value it points to and `Drop` so cleanup happens automatically. Rust's standard library gives you four foundational smart pointers, and knowing when each applies is central to writing idiomatic Rust beyond the borrow checker basics.

`Box<T>` is the simplest: it heap-allocates a single value and gives you sole ownership of it. You reach for it when a value's size is not known at compile time (recursive types like a linked list or tree need indirection through a Box to have a finite size), when you want to move a large value without copying its bytes, or when you need a trait object such as `Box<dyn Error>`. A Box is a single owner, just like a plain stack value, so it follows ordinary move semantics.

`Rc<T>` (reference counted) lets multiple owners share the same heap value. Each `clone` bumps a counter instead of copying the data, and the value is dropped only when the last Rc goes away. Rc is for single-threaded graphs where ownership is genuinely shared - the classic case is a node referenced by several parents. Rc gives you shared, immutable access; it is not thread-safe because its counter is not atomic.

`Arc<T>` (atomically reference counted) is the thread-safe sibling of Rc. It uses atomic operations on the count so it can be sent across threads, at a small performance cost. Use Arc whenever shared ownership crosses a thread boundary, typically paired with a Mutex or RwLock for shared mutation.

`RefCell<T>` provides interior mutability: it lets you mutate data through a shared reference by moving the borrow rules from compile time to runtime. `borrow` and `borrow_mut` hand out guards and panic if you violate the aliasing rule (many readers or one writer) while running. It is single-threaded; the thread-safe analog is Mutex. The common combination `Rc<RefCell<T>>` gives you shared, mutable, single-threaded state - powerful but easy to misuse, since RefCell turns borrow violations into runtime panics rather than compile errors.

Common Misconception

Rc<T> lets you mutate shared data and Arc is just a faster Rc. In reality Rc and Arc give only shared immutable access, you need RefCell or Mutex inside them to mutate, and Arc is slower than Rc because its counter uses atomic operations for thread safety.

Why It Matters

Choosing the wrong smart pointer leads to compile errors that block sharing, RefCell panics that crash at runtime instead of being caught by the compiler, or needless atomic overhead from using Arc where single-threaded Rc would do.

Common Mistakes

  • Trying to mutate the value inside an Rc directly, forgetting that Rc gives only shared immutable access and you need an inner RefCell or Mutex.
  • Using Arc in single-threaded code where Rc is cheaper because it avoids atomic reference-count operations.
  • Holding two borrow_mut guards (or a borrow and a borrow_mut) on the same RefCell at runtime, causing an 'already borrowed' panic.
  • Creating reference cycles with Rc<RefCell<T>> that never get dropped, leaking memory because the count never reaches zero.
  • Reaching for Box<T> when a plain stack value would work, adding an unnecessary heap allocation.

Avoid When

  • A plain stack value or reference already satisfies the borrow checker, where adding Box or Rc just introduces an allocation or runtime overhead.
  • You need shared mutation across threads, where Rc<RefCell<T>> will not compile and you must use Arc<Mutex<T>> instead.
  • Your data forms cycles that Rc cannot drop, where you need Weak references or a different ownership design to avoid leaks.
  • Compile-time borrow checking already expresses the access pattern, so RefCell would only move safety checks to a runtime panic for no benefit.

When To Use

  • Boxing a recursive type like a tree or linked list so it has a known finite size, or storing a trait object as Box<dyn Trait>.
  • Sharing read-only ownership of a value among several owners in single-threaded code with Rc.
  • Sharing ownership across thread boundaries with Arc, usually combined with Mutex or RwLock for mutation.
  • Mutating data behind a shared reference where the borrow rules cannot be proven at compile time, using RefCell for runtime-checked interior mutability.

Code Examples

✗ Vulnerable
use std::rc::Rc;

#[derive(Debug)]
struct Counter {
    value: i32,
}

fn main() {
    let shared = Rc::new(Counter { value: 0 });
    let other = Rc::clone(&shared);

    // Error: cannot mutate through an Rc - it gives shared, immutable access.
    // shared.value += 1;

    // Reaching for Arc here is needless: this is all single-threaded,
    // so the atomic counter just adds overhead.
    // let shared = std::sync::Arc::new(Counter { value: 0 });

    println!("{:?} {:?}", shared, other);
}
✓ Fixed
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Counter {
    value: i32,
}

fn main() {
    // Rc shares ownership; RefCell adds runtime-checked interior mutability.
    let shared = Rc::new(RefCell::new(Counter { value: 0 }));
    let other = Rc::clone(&shared);

    // Mutate through the shared reference via borrow_mut.
    shared.borrow_mut().value += 1;
    other.borrow_mut().value += 1;

    // Read access via borrow; the guard is dropped at the end of the statement.
    println!("value is {}", shared.borrow().value); // prints 2
}

Added 9 Jun 2026
Views 3
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 1 ping T 1 ping W
Google 1
Google 1
Google 2
crawler 2
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
Pick by need: Box for single heap ownership, Rc for shared single-threaded ownership, Arc for shared cross-thread ownership, and wrap in RefCell (or Mutex for threads) when you need to mutate shared data.
📦 Applies To
web cli queue-worker library
🔗 Prerequisites
🔍 Detection Hints
Rc::new\(|Arc::new\(|RefCell::new\(|Box::new\(|\.borrow_mut\(\)
Auto-detectable: ✗ No clippy
🤖 AI Agent
Confidence: Medium False Positives: High ✗ Manual fix Fix: Medium Context: Function

✓ schema.org compliant