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>orRefCell<T>only when shared-handle mutation is genuinely simpler than&mut self. - Use
Weakto break parent-child and observer cycles. - Use
OnceLockorLazyLockfor one-time initialization. - Follow concurrency primitives for
Arc, locks, and channels.
Avoid
- Do not use
RcorRefCellacross 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
RefCellfor narrow single-threaded caches, adapters, or tests where runtime borrow checking is the clearest model. - Use
Boxonly when recursion, size, or owned dynamic dispatch requires it.