---
title: "21.6 RefCell and interior mutability"
description: "Moving the borrow check to runtime with RefCell, mutating through a shared reference safely, and the Rc<RefCell<T>> combination."
url: "https://learnrust.net/chapter-21/refcell-and-interior-mutability/"
last_updated: "2026-06-13"
---

# 21.6 RefCell and interior mutability

Chapter 9 gave you a rule with no exceptions: at any moment a value may have many shared references (`&T`) *or* one mutable reference (`&mut T`), never both. The compiler enforces it, and code that breaks it doesn't build. That rule is the foundation of Rust's safety, and you should keep trusting it. But it's enforced at *compile time*, which means the compiler must be conservative: it rejects anything it can't *prove* safe, even patterns that are actually fine. **Interior mutability** is the controlled escape hatch for those cases, and **`RefCell<T>`** is the tool. This also pays off a promissory note: lesson [5.1](https://learnrust.net/chapter-5/constants-and-statics/) named "interior mutability" as a thing `static` related to, and left it for later. Later is here.

## The idea: same rule, checked at runtime

`RefCell<T>` holds a value and lets you borrow it mutably *even when the `RefCell` itself is only shared*. It doesn't abandon chapter 9's rule, many readers or one writer; it still enforces exactly that rule. The difference is *when*: `RefCell` checks it at **run time** instead of compile time. You ask a `RefCell` for a borrow with a method call, and it tracks the borrows itself; if you violate the rule, it doesn't fail to compile, it **panics** at the moment of the violation.

That trade, runtime check instead of compile-time check, buys flexibility (patterns the compiler couldn't prove become possible) at a cost (a small runtime overhead, and a violation becomes a panic rather than a build error). You reach for it only when you, the programmer, can see that an access is safe but the compiler can't.

## borrow and borrow_mut

`RefCell` replaces `&` and `&mut` with two methods. `borrow()` gives a shared borrow (a `Ref<T>`, usable like `&T`); `borrow_mut()` gives a mutable borrow (a `RefMut<T>`, usable like `&mut T`). Here's mutation through a `RefCell` that is itself not declared `mut`:

```rust
use std::cell::RefCell;

fn main() {
    let cell = RefCell::new(5);   // note: not `let mut`

    *cell.borrow_mut() += 10;     // mutate through a shared RefCell

    println!("{}", cell.borrow());
}
```

```
15
```

Look at what just happened: `cell` isn't `mut`, yet we mutated the value inside it. That's the "interior" in interior mutability, the mutability lives *inside* the `RefCell`, not in the binding. `borrow_mut()` hands out a `RefMut` that we dereference with `*` to add 10; the borrow ends at the semicolon. Then `borrow()` reads it back. Each call checks, right then, that the borrowing rules are satisfied.

## Break the rule, get a panic

The rule is still the rule. If you hold two mutable borrows at once, `RefCell` catches it, at run time, with a panic:

```rust
use std::cell::RefCell;

fn main() {
    let cell = RefCell::new(String::from("hi"));

    let mut first = cell.borrow_mut();
    let mut second = cell.borrow_mut();   // second mutable borrow: panics here

    first.push('!');
    second.push('?');
}
```

```
thread 'main' (12345) panicked at src/main.rs:8:30:
already borrowed: BorrowMutError
```

This is the same error chapter 9 would have caught at compile time (E0499, two mutable borrows), but because we used `RefCell`, it's deferred to run time and surfaces as a `BorrowMutError` panic the instant the second `borrow_mut()` runs. That's the cost of interior mutability laid bare: you trade a friendly compile error for a crash, and you take on the responsibility the compiler used to carry. Use `RefCell` only where the flexibility is worth that trade.

> **RefCell moves your bugs from compile time to run time**
>
> With ordinary `&mut`, a borrow-rule violation is a *compile error*: it can't ship. With `RefCell`, the same violation is a *runtime panic*: it compiles fine and blows up only when that code path runs, possibly in production, possibly only under load. That's strictly more dangerous, which is why `RefCell` is a tool of last resort, not a way to "turn off the borrow checker" when it's inconvenient. If `&` and `&mut` can express what you need, use them. Reach for `RefCell` only when a legitimate pattern genuinely requires runtime-checked borrowing.

## The classic combination: Rc<RefCell<T>>

`RefCell` becomes genuinely powerful paired with `Rc` from the last lesson. Recall the two halves: `Rc<T>` gives *many owners* but only shared (read-only) access; `RefCell<T>` gives *mutation* but only single ownership. Neither alone lets you have data that's both shared *and* mutable. Nest them, `Rc<RefCell<T>>`, and you get exactly that: multiple owners, each able to mutate the shared value.

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

fn main() {
    let shared = Rc::new(RefCell::new(vec![1, 2, 3]));

    let a = Rc::clone(&shared);
    let b = Rc::clone(&shared);

    a.borrow_mut().push(4);   // mutate through one handle
    b.borrow_mut().push(5);   // mutate through another

    println!("{:?}", shared.borrow());
}
```

```
[1, 2, 3, 4, 5]
```

`shared`, `a`, and `b` are three owners of the same `RefCell<Vec<i32>>` (the `Rc` layer). Through any of them, `borrow_mut()` reaches in and mutates the vector (the `RefCell` layer). Both pushes land on the one shared vector. This `Rc<RefCell<T>>` pattern, shared ownership of mutable data, is the standard building block for flexible, interconnected data structures in single-threaded Rust: a tree whose nodes can be edited through multiple references, an observer list, a shared cache. The borrow rules still hold; they're just enforced per-`borrow_mut()`-call at run time.

> **Key insight**
>
> The three smart pointers compose along two independent axes. `Box<T>`: one owner, mutate via normal `&mut`. `Rc<T>`: many owners, read-only. `RefCell<T>`: one owner, runtime-checked mutation. `Rc<RefCell<T>>`: many owners *and* runtime-checked mutation. You pick the combination by answering two questions, "how many owners?" and "do I need to mutate shared data?", and the borrow rules of chapter 9 are never actually broken, only moved to run time where `RefCell` is involved.

## Quiz time

**Question #1**

What does `RefCell<T>` change about the borrowing rules, and what doesn't it change?

<details class="solution">
<summary>Show solution</summary>

It changes *when* the rules are enforced: `RefCell` checks the many-readers-or-one-writer rule at *run time* (via `borrow()`/`borrow_mut()`) instead of compile time, which lets you mutate through a shared `RefCell` and enables patterns the compiler couldn't prove safe. It does *not* change the rule itself, you still can't have a mutable borrow alongside any other borrow. Breaking the rule panics (`BorrowMutError`) instead of failing to compile.

</details>

**Question #2**

What is the downside of `RefCell` compared to ordinary `&mut`?

<details class="solution">
<summary>Show solution</summary>

A borrow-rule violation becomes a *runtime panic* instead of a *compile error*. With `&mut`, the bug can't ship; with `RefCell`, it compiles and only crashes when that code path runs (possibly in production). There's also a small runtime cost for the borrow tracking. So `RefCell` trades compile-time guarantees for flexibility, and should be used only when a pattern genuinely needs runtime-checked borrowing.

</details>

**Question #3**

Why combine `Rc` and `RefCell` as `Rc<RefCell<T>>`? What does each layer contribute?

<details class="solution">
<summary>Show solution</summary>

`Rc<T>` provides multiple ownership but only shared (read-only) access; `RefCell<T>` provides mutation but only single ownership. Neither alone gives shared *and* mutable data. Nesting them, `Rc<RefCell<T>>`, combines both: the `Rc` layer allows many owners (clone the handle), and the `RefCell` layer allows any owner to mutate the value via `borrow_mut()` (runtime-checked). It's the standard pattern for shared, mutable data structures in single-threaded code.

</details>

`Rc` and `RefCell` unlock flexible shared structures, but they also open the door to Rust's one memory-safety gap. The final lesson ([21.7](https://learnrust.net/chapter-21/reference-cycles-and-weak/)) shows how two `Rc`s pointing at each other can leak memory, and how `Weak` references break the cycle.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
