In this lesson, you will learn how to modify data even when you only have an immutable reference to the container holding that data.
The Problem with "Immutable"
Up until now, you have learned that the Rust borrow checker enforces a strict rule: if you have an immutable reference (&T) to a piece of data, that data cannot change. This is a fundamental guardrail that prevents data races and memory corruption.
However, sometimes you have a data structure that should be logically immutable from the outside, but needs to update its internal state to function. If we followed the rules strictly, we would need a mutable reference (&mut T) to change anything. But what if we are working in a context where we only have access to an immutable reference?
The "Interior Mutability" Solution
"Interior Mutability" is a design pattern that allows us to bypass the compiler's usual mutability checks by moving the "check" from compile-time to runtime.
We do this by using special wrapper types. These types look like immutable containers to the outside world, but they contain logic that allows us to perform a "hidden" mutation inside. Think of it like a secure vault: the vault itself is locked in place (the immutable reference), but you have a small, controlled slot that allows you to swap out an item inside if you follow specific safety rules.
How it works
When you use a type like Cell or RefCell (the most common tools for this pattern), you are essentially promising the compiler: "I know this looks immutable, but I will manage the safety rules manually at runtime."
use std::cell::Cell;
fn main() {
// We bind this to an immutable variable
let data = Cell::new(10);
// We can "mutate" the interior even though 'data' is immutable
data.set(20);
println!("New value: {}", data.get());
}
In this code, we never took a mutable reference to data. We simply asked the Cell to update its internal value. The Cell handles the safety logic, ensuring that we aren't violating Rust’s core principles while we make our change.
Key Takeaway: Interior mutability allows you to mutate data behind an immutable reference by shifting the burden of safety from the compile-time borrow checker to runtime checks.