← Back to Rust Course | Chapter 13: Smart Pointers & Unsafe | Lesson 3 of 7

RefCell and Interior Mutability

RefCell lets you change a value even through a reference that's supposed to be read-only, but it checks the rules while your program runs instead of ahead of time.
Syntax
rust
let name = RefCell::new(value);
name.borrow();
name.borrow_mut();

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

Mutating Through RefCell

RefCell<T> lets you obtain a mutable reference to its inner data via .borrow_mut(), even when the RefCell itself is bound immutably, moving borrow checking to runtime.

Example: Mutating Through RefCell

markup
// Bring `std::cell::RefCell` into scope
use std::cell::RefCell;

// Entry point: execution of the program starts here
fn main() {
    // Declare an immutable variable `cell`, set to `RefCell::new(5)`
    let cell = RefCell::new(5);
    *cell.borrow_mut() += 1;
    // Print "{}" to the console, substituting in `cell.borrow()` (with a trailing newline)
    println!("{}", cell.borrow());
}

Runtime Borrow Checking

Unlike compile-time references, RefCell checks borrowing rules while the program runs: attempting two conflicting borrows at once causes a panic rather than a compile error.

Example: Runtime Borrow Checking

markup
// Bring `std::cell::RefCell` into scope
use std::cell::RefCell;

// Entry point: execution of the program starts here
fn main() {
    // Declare an immutable variable `cell`, set to `RefCell::new(String::from("hello"))`
    let cell = RefCell::new(String::from("hello"));
    {
        // Declare an immutable variable `borrowed`, set to `cell.borrow()`
        let borrowed = cell.borrow();
        // Print "Reading: {}" to the console, substituting in `borrowed` (with a trailing newline)
        println!("Reading: {}", borrowed);
    }
    cell.borrow_mut().push_str(", world");
    // Print "{}" to the console, substituting in `cell.borrow()` (with a trailing newline)
    println!("{}", cell.borrow());
}

Combining Rc and RefCell

Rc<RefCell<T>> is a common pattern that gives multiple owners shared access to the same data, while still allowing any of them to mutate it through the RefCell.

Example: Combining Rc and RefCell

markup
// Bring `std::cell::RefCell` into scope
use std::cell::RefCell;
// Bring `std::rc::Rc` into scope
use std::rc::Rc;

// Entry point: execution of the program starts here
fn main() {
    // Declare an immutable variable `shared`, set to `Rc::new(RefCell::new(0))`
    let shared = Rc::new(RefCell::new(0));
    // Declare an immutable variable `clone_a`, set to `Rc::clone(&shared)`
    let clone_a = Rc::clone(&shared);
    *clone_a.borrow_mut() += 10;
    // Print "Value: {}" to the console, substituting in `shared.borrow()` (with a trailing newline)
    println!("Value: {}", shared.borrow());
}

Why Interior Mutability Exists

Interior mutability provides an escape hatch for situations where Rust's strict compile-time borrowing rules are too rigid, such as a shared cache updated from multiple places, at the cost of moving some checks to runtime.

Example: Why Interior Mutability Exists

markup
// Bring `std::cell::RefCell` into scope
use std::cell::RefCell;

// Define a struct `Cache` to group related fields together
struct Cache {
    value: RefCell<Option<i32>>,
}

// Entry point: execution of the program starts here
fn main() {
    // Declare an immutable variable `cache`, set to `Cache { value: RefCell::new(None) }`
    let cache = Cache { value: RefCell::new(None) };
    *cache.value.borrow_mut() = Some(42);
    // Print "{:?}" to the console, substituting in `cache.value.borrow()` (with a trailing newline)
    println!("{:?}", cache.value.borrow());
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Borrowing a RefCell mutably twice at the same time, which compiles fine but panics at runtime with a borrow error.
  2. Using RefCell when a plain &mut reference would work just as well, adding unnecessary runtime checking overhead.
  3. Forgetting RefCell alone doesn't allow sharing across owners -- it is usually combined with Rc for shared, mutable-looking data.
Chapter Summary
  • RefCell<T> allows mutating data even through an immutable reference, moving borrow checking from compile time to runtime.
  • .borrow() and .borrow_mut() return runtime-checked references, and violating borrowing rules causes a panic instead of a compile error.
  • Rc<RefCell<T>> is a common pattern combining shared ownership with interior mutability.
  • Interior mutability should be used deliberately, since misuse turns compile-time errors into runtime panics.
🔒

Chapter Quiz — Complete all 7 topics to unlock

0/7 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.