Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Smart Pointers and Interior Mutability

Rule

Prefer ordinary ownership; use Box for required indirection, Rc and RefCell only for single-threaded sharing or interior mutation, and OnceLock or LazyLock for one-time initialization.

Why

Smart pointers solve real ownership shapes but add allocation, runtime borrow checks, shared lifetime, or cycle risks.

Do

  • Use Box<T> for recursive data, large enum variants, or single-owner dynamic dispatch.
  • Use Rc<T> for single-threaded shared ownership.
  • Use Cell<T> or RefCell<T> only when shared-handle mutation is genuinely simpler than &mut self.
  • Use Weak to break parent-child and observer cycles.
  • Use OnceLock or LazyLock for one-time initialization.
  • Follow concurrency primitives for Arc, locks, and channels.

Avoid

  • Do not use Rc or RefCell across threads.
  • Do not create strong-reference cycles.
  • Do not add indirection or interior mutability by habit.
  • Do not create mutable global state without explicit initialization and access rules.

Example

#![allow(unused)]
fn main() {
use std::sync::OnceLock;

static DEFAULT_LOCALE: OnceLock<String> = OnceLock::new();

pub fn default_locale() -> &'static str {
    DEFAULT_LOCALE.get_or_init(|| "en-US".to_owned())
}

pub enum Expr {
    Literal(i64),
    Add(Box<Expr>, Box<Expr>),
}
}

Exceptions

  • Use RefCell for narrow single-threaded caches, adapters, or tests where runtime borrow checking is the clearest model.
  • Use Box only when recursion, size, or owned dynamic dispatch requires it.