In this lesson, you will learn how to modify the contents of a struct even when you only have an immutable reference to it.
The Rule of Immutability
So far, you have learned that if you have an immutable reference (&T) to a struct, you cannot change its fields. This is a core pillar of Rust’s safety—if you have a shared reference, the compiler guarantees that no one else is currently modifying that data, preventing data races.
But what if you have a scenario where the struct must remain shared, yet a specific part of it needs to be updated?
The Cell Pattern
The Cell pattern is the solution to this "immutable-but-changeable" dilemma. It relies on a special type called std::cell::Cell<T>.
When you wrap a piece of data inside a Cell, you are telling the compiler: "I want to be able to change this value, even if I only hold a shared reference to the container."
Under the hood, Cell works by using "unsafe" code to bypass the typical borrowing rules, but it exposes a safe interface for you. It achieves this by only allowing you to get and set the value, rather than providing a mutable reference to the inner data.
A Concrete Example
Imagine a counter that needs to be updated even when it is held by an immutable reference:
use std::cell::Cell;
struct Counter {
value: Cell<u32>,
}
fn increment(counter: &Counter) {
// We only have an immutable reference (&Counter),
// yet we can still modify the inner value!
let current = counter.value.get();
counter.value.set(current + 1);
}
In this code, &Counter is immutable. If value were a normal u32, the compiler would stop us from changing it. By using Cell, we can update the count without needing a &mut Counter.
The Cell type is designed specifically for simple, copyable types. Because it does not hand out references to the interior data, the borrow checker remains satisfied—you are replacing the value entirely rather than modifying it in place, which keeps the memory state predictable.
Key Takeaway: The Cell pattern allows you to bypass the constraints of immutable references for simple data types, enabling internal updates to structs that would otherwise be locked from modification.