Course contentsShow
← Rust
Lesson 1825 of 1,91016. Specialized Runtime TargetsFree lesson

The Role of Interior Mutability

Use Cell and RefCell types to manage mutable hardware state in a borrow-checker friendly way.

In this lesson, you will learn how Cell and RefCell allow you to bypass Rust’s strict borrowing rules to manage mutable hardware state in memory-mapped I/O environments.

The Conflict with Borrowing

In embedded systems, you often deal with "shared" hardware peripherals—like a timer or a GPIO port—that must be accessed from multiple contexts, such as an Interrupt Service Routine (ISR) and your main loop. Under normal Rust rules, if you hold a shared reference (&T), you cannot mutate the data. However, the hardware register behind that reference is inherently volatile and changes constantly. Cell and RefCell provide "interior mutability," allowing you to obtain a mutable reference to data even when you only have a shared reference to the container holding it.

Interior Mutability in Action

Cell<T> is the simplest form. It is designed for Copy types and performs no runtime checking. It effectively replaces the value inside it, which is ideal for simple flags or status registers:

use core::cell::Cell;

// A shared flag representing a hardware state
static INTERRUPT_OCCURRED: Cell<bool> = Cell::new(false);

fn main() {
    // We can "mutate" the state even through a shared reference
    INTERRUPT_OCCURRED.set(true);
}

RefCell<T> is used for more complex, non-Copy data structures. Unlike Cell, it enforces the borrowing rules at runtime. It keeps track of how many references currently exist; if you try to take a mutable reference (borrow_mut()) while an immutable one (borrow()) is already active, the code will panic.

Why Use This for Hardware?

In specialized runtime targets, using these types prevents you from needing global mutable state (which requires unsafe blocks and static mut). By wrapping a peripheral or a state tracker in a RefCell, you bridge the gap between Rust’s static safety guarantees and the dynamic reality of hardware that changes outside the compiler's view. It essentially moves the borrow checker's enforcement from compile-time (where it may be too rigid for shared hardware) to runtime, where you can handle the consequences of a conflict explicitly.

Key Takeaway: Interior mutability allows you to safely manage shared, volatile hardware state by shifting borrowing enforcement from the compiler to the runtime.