In this lesson, you will learn how to bypass the standard "immutable vs. mutable" borrow checker rules at runtime using Cell and RefCell.
The Problem: Strict Borrowing
Normally, Rust’s borrow checker is quite strict: if you have an immutable reference (&T) to a value, you cannot modify that value. This is a core part of Rust’s safety guarantee. However, sometimes you encounter a situation where you logically have an immutable object, but you need to update one specific internal field (like a counter or a cache). If you had to change the entire object to mut, it would force you to propagate that mutability everywhere in your code.
The Solution: Interior Mutability
The Interior Mutability Pattern allows you to mutate data even when you only have an immutable reference to the container. It achieves this by moving the borrow checking from compile-time (where the compiler stops you) to runtime (where the code panics if you break the rules).
Cell<T>: Used for simple types that implementCopy. It allows you to replace the value inside without needing a mutable reference.RefCell<T>: Used for more complex types. It allows you to borrow the data inside as mutable, even if theRefCellitself is immutable.
A Quick Example
Imagine you have an object that shouldn't change, but it needs to record how many times it was accessed:
use std::cell::Cell;
struct Logger {
access_count: Cell<u32>,
}
fn main() {
let logger = Logger { access_count: Cell::new(0) };
// Even though 'logger' is not marked 'mut', we can change the count!
let new_count = logger.access_count.get() + 1;
logger.access_count.set(new_count);
println!("Accessed {} times", logger.access_count.get());
}
By using Cell, we side-stepped the rule that forbids mutation through an immutable reference. With RefCell, you would use .borrow_mut() to gain access to the underlying data, but you must be careful: if you try to borrow it as mutable while it is already borrowed, your program will panic. Use these tools sparingly—they are a "trapdoor" for when the static borrow checker is too restrictive.
Key Takeaway: Interior mutability allows you to mutate data through an immutable reference by shifting borrow checking from compile-time to runtime.