| |

Rust 58 🦀 Interior Mutability Pattern (RefCell and Cell)

⏱️ Estimated read: 18 min | |

Rust’s borrow checker enforces a simple rule: a value has either one mutable reference or any number of shared references, never both. The rule is checked at compile time, and it is what makes data races impossible in safe code. But the rule is sometimes too strict. A value that is shared across many owners still needs to be mutated, or a callback must modify state captured by a shared reference. The interior mutability pattern relaxes the rule by moving the borrow check from compile time to runtime. The Cell<T> and RefCell<T> types are the two implementations. Cell<T> allows mutation through a shared reference by copying or replacing the value, and RefCell<T> allows mutation by tracking the borrows at runtime and panicking on a violation. Both are single-threaded; the multithreaded counterparts are Mutex<T> and RwLock<T>.

Key point: Interior mutability does not violate Rust’s safety rules. It moves the enforcement of the rules from compile time to runtime. A RefCell<T> that is borrowed mutably while a shared borrow is active panics, and the panic is a safe failure, not undefined behavior.


Why interior mutability exists

The shared-mutation problem. A value behind a shared reference cannot be mutated through the reference. If the value must change, and the reference must stay shared, the type must provide interior mutability. The classic example is a counter shared by many components: each component holds a shared reference, and the counter changes when any of them is used.

The callback problem. A closure captures a shared reference to a value, and the value must be mutated inside the closure. The borrow checker rejects the mutation because the closure’s captures are shared. RefCell<T> moves the check to runtime, and the mutation is allowed.

The trait-method problem. A trait method takes &self and must mutate a field of the implementing struct. The method signature cannot be changed to &mut self, because the trait is shared across types. Cell<T> or RefCell<T> in the field provides the interior mutability.

The graph problem. A graph has nodes that reference each other, and the references must be updatable. The Rc<RefCell<T>> combination provides shared ownership and mutation, which is the standard pattern for a mutable graph on a single thread.

The caching problem. A struct computes a value lazily and stores it in a field. The method that returns the value takes &self, but the field must be written. OnceCell<T> or RefCell<T> provides the interior mutability.

The observer problem. A subject holds a list of observers and notifies them when something changes. The list must be mutated through a shared reference, because the subject is shared. RefCell<Vec<Observer>> is the pattern.

The thread-safety problem. Cell<T> and RefCell<T> are not thread-safe. They use non-atomic operations and are marked !Sync. The multithreaded counterparts are Mutex<T> and RwLock<T>, which use atomic operations and locking.


a. The Cell type

Cell<T> provides interior mutability for types that are Copy or that can be replaced. The methods take &self, so they work through a shared reference.

The get and set

use std::cell::Cell;

fn main() {
    let c = Cell::new(5);
    println!("{}", c.get());  // 5
    c.set(10);
    println!("{}", c.get());  // 10
}

The get method returns a copy of the value, which is why the type must be Copy. The set method replaces the value.

The replace

use std::cell::Cell;

fn main() {
    let c = Cell::new(5);
    let old = c.replace(10);
    println!("{}", old);      // 5
    println!("{}", c.get());  // 10
}

The replace method swaps the value and returns the old one.

The take

use std::cell::Cell;

fn main() {
    let c = Cell::new(String::from("hello"));
    let value = c.take();
    println!("{}", value);    // hello
    // c is now empty.
}

The take method replaces the value with Default::default() and returns the old value. The take method is available for types that implement Default.

The update

use std::cell::Cell;

fn main() {
    let c = Cell::new(5);
    c.update(|v| v + 1);
    println!("{}", c.get());  // 6
}

The update method applies a function to the value and stores the result.

The interior mutability through a shared reference

use std::cell::Cell;

struct Counter {
    count: Cell<u32>,
}

impl Counter {
    fn increment(&self) {
        self.count.set(self.count.get() + 1);
    }

    fn value(&self) -> u32 {
        self.count.get()
    }
}

fn main() {
    let counter = Counter { count: Cell::new(0) };
    counter.increment();
    counter.increment();
    println!("{}", counter.value());  // 2
}

The increment method takes &self and mutates the count field. The Cell provides the interior mutability.

The Cell limitations

Cell<T> requires T: Copy for get, and it does not provide references to the value. A Cell<Vec<T>> cannot be mutated in place; the whole vector must be replaced. The type is appropriate for small, copyable values, not for large structures.

The Cell methods

MethodRequiresReturns
get()T: CopyA copy of the value
set(v)—()
replace(v)—The old value
take()T: DefaultThe old value
update(f)—()
swap(other)—()

b. The RefCell type

RefCell<T> provides interior mutability for any type, not just Copy types. The borrows are checked at runtime, and a violation panics.

The borrow and borrow_mut

use std::cell::RefCell;

fn main() {
    let c = RefCell::new(5);
    let r = c.borrow();
    println!("{}", *r);  // 5
    drop(r);

    let mut r = c.borrow_mut();
    *r = 10;
    drop(r);

    println!("{}", c.borrow());  // 10
}

The borrow method returns a Ref<T>, which is a shared reference. The borrow_mut method returns a RefMut<T>, which is a mutable reference. The two cannot be active at the same time.

The runtime check

use std::cell::RefCell;

fn main() {
    let c = RefCell::new(5);
    let r1 = c.borrow();
    let r2 = c.borrow();  // OK: multiple shared borrows
    drop(r1);
    drop(r2);

    let m = c.borrow_mut();
    // let m2 = c.borrow_mut();  // ❌ Panics
    drop(m);
}

A second borrow_mut while a RefMut is active panics. A borrow while a RefMut is active also panics.

The try_borrow and try_borrow_mut

use std::cell::RefCell;

fn main() {
    let c = RefCell::new(5);
    let m = c.borrow_mut();
    match c.try_borrow() {
        Ok(r) => println!("{}", *r),
        Err(_) => println!("Already mutably borrowed"),
    }
}

The try_borrow and try_borrow_mut methods return a Result instead of panicking. They are used when the borrow may fail and the failure is handled.

The interior mutability through a shared reference

use std::cell::RefCell;

struct Counter {
    count: RefCell<u32>,
}

impl Counter {
    fn increment(&self) {
        *self.count.borrow_mut() += 1;
    }

    fn value(&self) -> u32 {
        *self.count.borrow()
    }
}

fn main() {
    let counter = Counter { count: RefCell::new(0) };
    counter.increment();
    counter.increment();
    println!("{}", counter.value());  // 2
}

The Counter struct is shared, and the increment method mutates the count field through the RefCell. The borrow is checked at runtime.

The borrow tracking

The RefCell<T> keeps a borrow flag: zero or more shared borrows, or one mutable borrow. The flag is updated on each borrow and drop. The borrow_count method returns the current count.

let c = RefCell::new(5);
let r = c.borrow();
println!("{}", c.borrow_count());  // 1

The borrow rules

BorrowsState
0Idle
N shared (Ref)N readers
1 mutable (RefMut)1 writer
Shared + mutablePanic
2 mutablePanic

The Ref and RefMut types

The Ref<'a, T> and RefMut<'a, T> types implement Deref and DerefMut, so they behave like references. They release the borrow when they are dropped, which is why the borrow is tied to the lifetime of the guard.

{
    let r = c.borrow();
    // The borrow is active here.
}  // The borrow is released here.

The scope of the borrow is the scope of the guard.

The RefCell panics

The RefCell panics on a violation. The panic is a safe failure, not undefined behavior. The message describes the violation.

thread 'main' panicked at 'already borrowed: BorrowMutError'

The panic is the trade-off for the flexibility. A RefCell that is used carefully does not panic.

The RefCell and Rc

The Rc<RefCell<T>> combination provides shared mutable state on a single thread.

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

fn main() {
    let counter = Rc::new(RefCell::new(0));
    let a = Rc::clone(&counter);
    let b = Rc::clone(&counter);

    *a.borrow_mut() += 1;
    *b.borrow_mut() += 1;

    println!("{}", counter.borrow());  // 2
}

The Rc provides the shared ownership, and the RefCell provides the mutation. The combination is the standard pattern for shared mutable state.


c. The interior mutability patterns

The Cell<T> and RefCell<T> types are used in a small set of recurring patterns. The patterns are the reason the types exist.

The lazy initialization

A value is computed on first access and stored for later use.

use std::cell::RefCell;

struct Lazy {
    value: RefCell<Option<i32>>,
}

impl Lazy {
    fn get(&self) -> i32 {
        let mut v = self.value.borrow_mut();
        if v.is_none() {
            *v = Some(expensive_computation());
        }
        v.unwrap()
    }
}

fn expensive_computation() -> i32 {
    42
}

The get method takes &self and initializes the value on the first call. The RefCell provides the interior mutability. The OnceCell<T> and OnceLock<T> types are the specialized versions of this pattern.

The mutable counter

use std::cell::Cell;

struct ClickCounter {
    clicks: Cell<u32>,
}

impl ClickCounter {
    fn click(&self) {
        self.clicks.set(self.clicks.get() + 1);
    }
}

The counter is incremented through a shared reference. The Cell is the right choice because the value is Copy.

The observer list

use std::cell::RefCell;

struct Subject {
    observers: RefCell<Vec<Box<dyn Fn()>>>,
}

impl Subject {
    fn subscribe(&self, f: Box<dyn Fn()>) {
        self.observers.borrow_mut().push(f);
    }

    fn notify(&self) {
        for f in self.observers.borrow().iter() {
            f();
        }
    }
}

The Subject holds a list of observers and mutates the list through a shared reference. The RefCell provides the mutation.

The graph node

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

struct Node {
    value: i32,
    neighbors: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let a = Rc::new(Node { value: 1, neighbors: RefCell::new(vec![]) });
    let b = Rc::new(Node { value: 2, neighbors: RefCell::new(vec![]) });

    a.neighbors.borrow_mut().push(Rc::clone(&b));
    b.neighbors.borrow_mut().push(Rc::clone(&a));
}

The Rc<RefCell<T>> combination provides a mutable graph. The Rc provides the shared ownership, and the RefCell provides the mutation.

The thread-local state

The thread_local! macro and the Cell<T> combination provide per-thread state.

use std::cell::Cell;

thread_local! {
    static COUNTER: Cell<u32> = Cell::new(0);
}

fn main() {
    COUNTER.with(|c| c.set(c.get() + 1));
    COUNTER.with(|c| println!("{}", c.get()));  // 1
}

The thread_local! macro declares a variable that is unique per thread, and the Cell provides the mutation.

The memoization

use std::cell::RefCell;
use std::collections::HashMap;

struct Memo {
    cache: RefCell<HashMap<i32, i32>>,
}

impl Memo {
    fn compute(&self, n: i32) -> i32 {
        if let Some(&v) = self.cache.borrow().get(&n) {
            return v;
        }
        let v = n * n;
        self.cache.borrow_mut().insert(n, v);
        v
    }
}

The Memo struct caches the results of a computation. The RefCell provides the mutation of the cache through a shared reference.

The pattern table

PatternTypeUse Case
Lazy initRefCell<Option<T>>Compute once on first access
CounterCell<u32>Increment through &self
ObserverRefCell<Vec<F>>Mutable list through &self
GraphRc<RefCell<T>>Shared mutable graph
Thread-localthread_local! + Cell<T>Per-thread state
MemoizationRefCell<HashMap<K, V>>Cached computation

Complete Example Session

// ============================================
// PART 1: THE BASIC CELL
// ============================================

use std::cell::Cell;

fn main() {
    let c = Cell::new(5);
    println!("{}", c.get());  // 5
    c.set(10);
    println!("{}", c.get());  // 10
}

// ============================================
// PART 2: THE CELL REPLACE AND TAKE
// ============================================

fn main() {
    let c = Cell::new(5);
    let old = c.replace(10);
    println!("{}", old);  // 5

    let s = Cell::new(String::from("hello"));
    let value = s.take();
    println!("{}", value);  // hello
}

// ============================================
// PART 3: THE CELL COUNTER
// ============================================

struct Counter {
    count: Cell<u32>,
}

impl Counter {
    fn increment(&self) {
        self.count.set(self.count.get() + 1);
    }
}

fn main() {
    let counter = Counter { count: Cell::new(0) };
    counter.increment();
    counter.increment();
    println!("{}", counter.count.get());  // 2
}

// ============================================
// PART 4: THE BASIC REFCELL
// ============================================

use std::cell::RefCell;

fn main() {
    let c = RefCell::new(5);
    let r = c.borrow();
    println!("{}", *r);  // 5
    drop(r);

    let mut m = c.borrow_mut();
    *m = 10;
    drop(m);

    println!("{}", c.borrow());  // 10
}

// ============================================
// PART 5: THE RUNTIME PANIC
// ============================================

fn main() {
    let c = RefCell::new(5);
    let m = c.borrow_mut();
    // let r = c.borrow();  // ❌ Panics
    drop(m);
}

// ============================================
// PART 6: THE TRY_BORROW
// ============================================

fn main() {
    let c = RefCell::new(5);
    let m = c.borrow_mut();
    match c.try_borrow() {
        Ok(r) => println!("{}", *r),
        Err(_) => println!("Already mutably borrowed"),
    }
}

// ============================================
// PART 7: THE REFCELL COUNTER
// ============================================

struct RefCounter {
    count: RefCell<u32>,
}

impl RefCounter {
    fn increment(&self) {
        *self.count.borrow_mut() += 1;
    }
}

fn main() {
    let counter = RefCounter { count: RefCell::new(0) };
    counter.increment();
    counter.increment();
    println!("{}", counter.count.borrow());  // 2
}

// ============================================
// PART 8: THE RC<REFCELL<T>> PATTERN
// ============================================

use std::rc::Rc;

fn main() {
    let counter = Rc::new(RefCell::new(0));
    let a = Rc::clone(&counter);
    let b = Rc::clone(&counter);

    *a.borrow_mut() += 1;
    *b.borrow_mut() += 1;

    println!("{}", counter.borrow());  // 2
}

// ============================================
// PART 9: THE MEMOIZATION PATTERN
// ============================================

use std::collections::HashMap;

struct Memo {
    cache: RefCell<HashMap<i32, i32>>,
}

impl Memo {
    fn compute(&self, n: i32) -> i32 {
        if let Some(&v) = self.cache.borrow().get(&n) {
            return v;
        }
        let v = n * n;
        self.cache.borrow_mut().insert(n, v);
        v
    }
}

fn main() {
    let m = Memo { cache: RefCell::new(HashMap::new()) };
    println!("{}", m.compute(4));  // 16
    println!("{}", m.compute(4));  // 16 (cached)
}

// ============================================
// PART 10: THE THREAD-LOCAL PATTERN
// ============================================

thread_local! {
    static COUNTER: Cell<u32> = Cell::new(0);
}

fn main() {
    COUNTER.with(|c| c.set(c.get() + 1));
    COUNTER.with(|c| println!("{}", c.get()));  // 1
}

The ten parts showed the basic Cell, the replace and take, the Cell counter, the basic RefCell, the runtime panic, the try_borrow, the RefCell counter, the Rc<RefCell<T>> pattern, the memoization pattern, and the thread-local pattern.


Quick Reference

The Cell Methods

MethodRequiresReturns
get()T: CopyA copy of the value
set(v)—()
replace(v)—The old value
take()T: DefaultThe old value
update(f)—()
swap(other)—()

The RefCell Methods

MethodReturnsPanics
borrow()Ref<T>On mutable borrow
borrow_mut()RefMut<T>On any borrow
try_borrow()Result<Ref<T>, _>No
try_borrow_mut()Result<RefMut<T>, _>No
borrow_count()usizeNo

The Borrow Rules

StateAllowed
No borrowborrow or borrow_mut
N sharedMore borrow
1 mutableNothing
Shared + mutablePanic

The Cell vs RefCell

AspectCell<T>RefCell<T>
Copy requiredFor getNo
ReferencesNoYes
Runtime checkNoYes
PanicsNoYes
OverheadNoneBorrow flag

The Thread-Safe Counterparts

Single-threadedMultithreaded
Cell<T>AtomicUsize, AtomicBool
RefCell<T>Mutex<T>, RwLock<T>
Rc<T>Arc<T>
Rc<RefCell<T>>Arc<Mutex<T>>

Best Practices

✅ Do This:

// Use Cell for Copy types.
let counter = Cell::new(0);
counter.set(counter.get() + 1);  // ✅

// Use RefCell for non-Copy types.
let list = RefCell::new(vec![1, 2, 3]);
list.borrow_mut().push(4);  // ✅

// Keep the borrow scope short.
{
    let mut v = list.borrow_mut();
    v.push(4);
}  // ✅

// Use try_borrow when the failure is expected.
if let Ok(r) = cell.try_borrow() { }  // ✅

// Use Rc<RefCell<T>> for shared mutable state.
let shared = Rc::new(RefCell::new(0));  // ✅

// Use the thread-safe counterparts across threads.
let shared = Arc::new(Mutex::new(0));  // ✅

// Use OnceCell for lazy initialization.
let value = OnceCell::new();  // ✅

❌ Don’t Do This:

// Don't hold a borrow across a function call.
let r = cell.borrow();
other_function();  // ❌ May panic
drop(r);

// Don't use RefCell across threads.
let c = RefCell::new(0);
thread::spawn(move || { c.borrow_mut(); });  // ❌ Not Sync

// Don't use Cell for non-Copy types.
let c = Cell::new(vec![1, 2, 3]);
c.get();  // ❌ Vec is not Copy

// Don't ignore the panic.
// A double borrow_mut is a bug.  // ❌

// Don't use RefCell when a plain reference works.
fn process(v: &Vec<i32>) { }  // ✅ Use a reference

Common Pitfalls

PitfallWhy It HappensFix
Panic on borrowDouble borrow_mutShorten the borrow
Not SyncRefCell across threadsUse Mutex
get on non-CopyCell<Vec<T>>Use RefCell
Borrow across callGuard not droppedUse a block
Leaked guardmem::forget on RefDo not forget the guard
Borrow not releasedGuard held too longDrop the guard

Real-World Examples

1. Cell Counter

struct Counter { count: Cell<u32> }

2. RefCell List

let list = RefCell::new(vec![1, 2, 3]);

3. Rc<RefCell>

let shared = Rc::new(RefCell::new(0));

4. try_borrow

if let Ok(r) = cell.try_borrow() { }

5. Lazy Init

let value = OnceCell::new();

6. Memoization

cache: RefCell<HashMap<K, V>>

7. Thread-local

thread_local! { static C: Cell<u32> = Cell::new(0); }

8. Graph Node

neighbors: RefCell<Vec<Rc<Node>>>

9. Observer

observers: RefCell<Vec<Box<dyn Fn()>>>

10. Arc<Mutex>

let data = Arc::new(Mutex::new(0));

Visual

The Cell

┌────────────────────────────────────────────────────────────┐
│  Cell<T>                                                   │
│                                                            │
│  struct Counter { count: Cell<u32> }                       │
│                                                            │
│  &Counter                                                  │
│  ┌──────────────────────────────────────────────────┐     │
│  │  count: Cell<u32>                                 │     │
│  │    ┌─────────────────┐                            │     │
│  │    │  value: 5       │                            │     │
│  │    └─────────────────┘                            │     │
│  │                                                   │     │
│  │  counter.increment() takes &self                  │     │
│  │  self.count.set(self.count.get() + 1)             │     │
│  │                                                   │     │
│  │  The Cell allows mutation through a shared ref.   │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  T must be Copy for get().                                 │
│  No runtime check, no panic.                               │
└────────────────────────────────────────────────────────────┘

The RefCell

┌────────────────────────────────────────────────────────────┐
│  RefCell<T>                                                │
│                                                            │
│  let c = RefCell::new(5);                                  │
│                                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │  RefCell                                          │     │
│  │    borrow_flag: 0                                 │     │
│  │    value: 5                                       │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  c.borrow()                                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  borrow_flag: 1                                   │     │
│  │  Ref<T> returned                                  │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  c.borrow_mut() while borrow is active                     │
│  ┌──────────────────────────────────────────────────┐     │
│  │  ❌ PANIC: already borrowed                        │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  The borrow flag is checked at runtime.                    │
└────────────────────────────────────────────────────────────┘

The Rc<RefCell> Pattern

┌────────────────────────────────────────────────────────────┐
│  Rc<RefCell<T>> PATTERN                                    │
│                                                            │
│  STACK                          HEAP                       │
│  ┌──────────┐                   ┌──────────────────────┐  │
│  │ a: ptr ──┼──────────────────▶│ strong: 2            │  │
│  ├──────────┤                   │ weak: 0              │  │
│  │ b: ptr ──┼──────────────────▶│ RefCell              │  │
│  └──────────┘                   │   borrow_flag: 0     │  │
│                                  │   value: 0           │  │
│                                  └──────────────────────┘  │
│                                                            │
│  *a.borrow_mut() += 1;                                     │
│  ┌──────────────────────────────────────────────────┐     │
│  │  a borrows the RefCell mutably, increments.       │     │
│  │  The RefMut guard is dropped, the flag resets.    │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  Rc: shared ownership. RefCell: interior mutability.       │
└────────────────────────────────────────────────────────────┘

The Borrow Rules

┌────────────────────────────────────────────────────────────┐
│  THE BORROW RULES                                          │
│                                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │  borrow_flag = 0                                  │     │
│  │    borrow()     → flag = 1,  Ref<T>               │     │
│  │    borrow_mut() → flag = -1, RefMut<T>            │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │  borrow_flag = N (shared borrows active)          │     │
│  │    borrow()     → flag = N+1                      │     │
│  │    borrow_mut() → ❌ PANIC                        │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │  borrow_flag = -1 (mutable borrow active)         │     │
│  │    borrow()     → ❌ PANIC                        │     │
│  │    borrow_mut() → ❌ PANIC                        │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  The flag is restored when the guard is dropped.           │
└────────────────────────────────────────────────────────────┘

Summary

ItemValue
Interior mutabilityMutation through a shared reference
Cell<T>For Copy types; no references
RefCell<T>For any type; runtime borrow check
get / setCell read and write
borrow / borrow_mutRefCell read and write
try_borrow / try_borrow_mutNon-panicking variants
Rc<RefCell<T>>Shared mutable single-threaded state
Arc<Mutex<T>>Shared mutable multithreaded state
Ref<T> / RefMut<T>The borrow guards
OnceCell<T>Lazy initialization

Key takeaways:

  • Interior mutability moves the borrow check from compile time to runtime. The Cell<T> and RefCell<T> types allow mutation through a shared reference, which the borrow checker would normally reject. The runtime check is the trade-off for the flexibility.
  • Cell<T> is for Copy types and provides no references. The get method returns a copy, and the set method replaces the value. The type has no runtime overhead beyond the value itself.
  • RefCell<T> works with any type and provides references. The borrow and borrow_mut methods return guards that implement Deref and DerefMut. The borrows are tracked with a flag, and a violation panics.
  • The try_borrow and try_borrow_mut methods do not panic. They return a Result that the caller can handle. They are used when the borrow may fail and the failure is expected.
  • The Rc<RefCell<T>> combination is the standard for shared mutable state. The Rc provides the shared ownership, and the RefCell provides the mutation. The combination is single-threaded.
  • The Arc<Mutex<T>> combination is the multithreaded counterpart. The Arc provides the atomic shared ownership, and the Mutex provides the thread-safe mutation.
  • A RefCell panic is a safe failure. The panic is the runtime’s response to a borrow violation, and it is not undefined behavior. The message describes the violation.
  • The borrow scope is the guard’s scope. A Ref or RefMut guard holds the borrow until it is dropped. Holding a guard across a function call is the most common cause of a panic.
  • Cell<T> and RefCell<T> are not thread-safe. They are marked !Sync, and the compiler rejects sending them across threads. The Mutex<T> and RwLock<T> types are the thread-safe alternatives.

Remember: Interior mutability is the pattern that lets a value be mutated through a shared reference. The Cell<T> type is for small, Copy values and provides no references. The RefCell<T> type works with any type and provides references with runtime borrow checking. The Rc<RefCell<T>> combination is the standard for shared mutable state on a single thread, and the Arc<Mutex<T>> combination is the multithreaded counterpart. The runtime check is the trade-off: the borrow rules are enforced at runtime instead of compile time, and a violation panics. The result is the flexibility of shared mutation without the unsafety of raw pointers, and a runtime check that is predictable and well-defined.


Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!