Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Main page
Recent changes
Random page
Help about MediaWiki
Special pages
JOHNWICK
Search
Search
Appearance
Create account
Log in
Personal tools
Create account
Log in
Pages for logged out editors
learn more
Contributions
Talk
Editing
Interior Mutability in Rust
Page
Discussion
English
Read
Edit
View history
Tools
Tools
move to sidebar
hide
Actions
Read
Edit
View history
General
What links here
Related changes
Page information
Appearance
move to sidebar
hide
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
In this article, I will talk about what Interior mutability in Rust is and where its needed with some practical examples and how it can be implemented. Interior mutability is a design pattern in Rust that allows you to mutate data even when there are immutable references to that data. This sounds a bit counter-intuititve to the whole Rust ownership and borrow philosophy. But there are practical use cases where such an implementation is required and we will explore them first. [[file:Interior_Mutability_in_Rust.jpg|500px]] Implementing a cache which can be shared between multiple parts of the same application. Lets say, we have simple cache which is implemented as a Hashmap between two strings and we want this to be shared between multiple parts of the same application. It can be implemented as follows. <pre> // A cache that we want to share between multiple owners struct Cache { data: HashMap<String, String>, } impl Cache { fn new() -> Self { Cache { data: HashMap::new(), } } fn get(&self, key: &str) -> Option<&String> { self.data.get(key) } // This needs &mut self to modify the HashMap fn insert(&mut self, key: String, value: String) { self.data.insert(key, value); } } </pre> Now lets implement a user of this Cache in a simple main() function. For simplicity sake, we will create the multiple users in the main directly. In Rust, if we want make a data structure of type T a shared data structure, we can start with an Rc<T> which is a reference counting smart pointer. This allows use to make clones of the reference to our Cache. So a possible implementation would look as follows. <pre> fn main() { // We want multiple owners of the same cache let cache = Rc::new(Cache::new()); let cache_clone1 = Rc::clone(&cache); let cache_clone2 = Rc::clone(&cache); // ERROR: Cannot borrow as mutable through Rc! // Rc only gives us shared references (&T), never mutable ones (&mut T) cache_clone1.insert("key1".to_string(), "value1".to_string()); // Even this won't work: let mut cache = Rc::new(Cache::new()); cache.insert(...); // Still can't mutate through Rc println!("Problem: We have multiple owners (Rc) but need to mutate the cache!"); println!("Rc<T> only provides shared access, never mutable access."); } </pre> Here we have two clones which are immutable references to our cache, but we can’t modify the cache using the insert method as the clones are not mutable. Rust’s borrowing rules prevent getting &mut T when multiple owners exist. This is a fundamental conflict: multiple ownership vs. mutation. Here is the error that the compiler will give if you compile the above code. <pre> error[E0596]: cannot borrow data in an `Rc` as mutable --> src/CacheWithRc/cache_with_Rc.rs:34:5 | 34 | cache_clone1.insert("key1".to_string(), "value1".to_string()); | ^^^^^^^^^^^^ cannot borrow as mutable | = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<Cache>` </pre> So, we need a way to modify the internal contents of a data structure(the hashmap inside the cache) even though the external container(the reference to cache) is immutable. Implementing Mock objects for testing. This is an example from The Rust Programming Language — The Rust Programming Language in the section about Interior mutablity. During testing we often need a test double, which is a different type than the original type. The internal implementation of these test doubles will enable better testability. We implement these test doubles as Mock Objects. In the below example, let’s say we have a library that is being implemented to support a limit tracking functionality where if the usage crosses certain limits a message(could be a warning, error etc) is to be generated. The implementation of the library expects the user to provide the mechanism for sending/consuming the messages. The library doesn’t need to know that detail. All it needs is something that implements a trait we’ll provide called Messenger. <pre> pub trait Messenger { fn send(&self, msg: &str); } pub struct LimitTracker<'a, T: Messenger> { messenger: &'a T, value: usize, max: usize, } impl<'a, T> LimitTracker<'a, T> where T: Messenger, { pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> { LimitTracker { messenger, value: 0, max, } } pub fn set_value(&mut self, value: usize) { self.value = value; let percentage_of_max = self.value as f64 / self.max as f64; if percentage_of_max >= 1.0 { self.messenger.send("Error: You are over your quota!"); } else if percentage_of_max >= 0.9 { self.messenger .send("Urgent warning: You've used up over 90% of your quota!"); } else if percentage_of_max >= 0.75 { self.messenger .send("Warning: You've used up over 75% of your quota!"); } } } </pre> This Messenger trait has one method called send that takes an immutable reference to self and the text of the message as arguments. This trait is the interface our mock object needs to implement so that the mock can be used in the same way a real object is. The other important part is that we want to test the behavior of the set_value method on the LimitTracker. The simplest way to do this would be to create a MockMessenger structure and have these messages stored for any test validation purposes and implement the Messenger trait for this structure. <pre> struct MockMessenger { sent_messages: Vec<String>, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: vec![], } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.push(String::from(message)); } } </pre> Now we can implement a test case which validates that the library API set_value generates a specific message when its called with certain value. <pre> fn it_sends_an_over_75_percent_warning_message() { let mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mock_messenger, 100); limit_tracker.set_value(80); assert_eq!(mock_messenger.sent_messages.len(), 1); assert_eq!(mock_messenger.sent_messages[0], "Warning: You've used up over 75% of your quota!"); } </pre> But, if you observe carefully, the send method implemented for our MockMessenger doesn’t compile due to the following error. <pre> error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference --> src/MockWithOutRefCell/mock_without_refCell.rs:58:13 | 58 | self.sent_messages.push(String::from(message)); | ^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable | </pre> Here we have an immutable reference to self (MockMessenger) which is passed by the test case and Rust compiler doesn’t allow us to modify the contents inside this MockMessenger object. Here again we see an use case where we want a mutable access to some data inside a container for which we have a immutable reference. Now, lets quickly look at what Interior mutability is and how it helps us to address both these use cases. In Rust std library we already have a construct called RefCell<T> which implements this Interior mutability pattern. With references and Box<T>, the borrowing rules’ invariants are enforced at compile time. With RefCell<T>, these invariants are enforced at runtime and your program will panic and exit if you break these rules. RefCell<T> provides two methods to access T. These methods apply the borrow checking rules at runtime and the program might panic if you violate them. borrow() -> returns the smart pointer type Ref<T> borrow_mut() -> returns the smart pointer type RefMut<T> Implementing a Cache that is shared between multiple parts of the same program. We need to wrap the cache inside a RefCell<T> and then use an Rc<T>. Then we can clone this immutable reference multiple times and thereby have multiple parts access the same cache. <pre> // RefCell provides interior mutability! // Rc provides shared ownership, RefCell allows mutation through shared reference let cache = Rc::new(RefCell::new(Cache::new())); let cache_clone1 = Rc::clone(&cache); let cache_clone2 = Rc::clone(&cache); Then we can use borrow_mut() when we want to call the insert() method and borrow() when we want call the get() method as shown below. // borrow_mut() gives us a mutable reference at runtime cache_clone1.borrow_mut().insert("user:1".to_string(), "Alice".to_string()); cache_clone2.borrow_mut().insert("user:2".to_string(), "Bob".to_string()); // borrow() gives us a shared reference at runtime if let Some(name) = cache.borrow().get("user:1") { println!("Found user:1 = {}", name); } </pre> Please note that we cannot have multiple mutable references at any time or have an immutable reference when there is already a mutable reference. The borrowing rules remains the same, but these are applied at runtime and the following code will panic. <pre> //Attempt to have two mutable borrow at the same time. let mut cache_clone1_mut_borrow = cache_clone1.borrow_mut(); cache_clone1_mut_borrow.insert("user:1".to_string(), "Alice".to_string()); //Panic in the below line as already a mutable borrow is active cache_clone2.borrow_mut().insert("user:2".to_string(), "Bob".to_string()); //Attempt to have an immutable borrow when a mutable borrow already exists. let mut cache_clone1_mut_borrow = cache_clone1.borrow_mut(); cache_clone1_mut_borrow.insert("user:1".to_string(), "Alice".to_string()); //The below line will Panic as we are trying to take an immutable borrow //when a mutable borrow is still active let cache_clone1_borrow = cache_clone1.borrow(); </pre> Implementing the Mock Object for testing We need to store the sent_messages vector as RefCell<T> inside the MockMessengeer, so that we can get a mutable reference to it from the send() method. <pre> struct MockMessenger { sent_messages: RefCell<Vec<String>>, } ... impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.borrow_mut().push(String::from(message)); } } </pre> Internals of Interior mutability Now lets briefly explore on how a RefCell<T> is implemented to bypass the Rust’s compile time borrow checking rules to get the desired behavior. Rust std library provides a core primitive called UnsafeCell<T> which enables the implementation of the Interior Mutability pattern. It’s a language feature that tells the compiler: “mutation through &T is possible here”. It provides .get() method that returns *mut T (raw mutable pointer) from &self. The below code will print 100 as we are able to modify the value pointed to by the reference cell. <pre> let cell = UnsafeCell::new(42); // UnsafeCell::get() takes &self but returns *mut T (raw mutable pointer) // This is the ONLY safe way to get interior mutability unsafe { let ptr = cell.get(); *ptr = 100; // Mutate through shared reference! println!("Value after mutation: {}", *ptr); } </pre> The implementations which provide the Interior Mutability pattern needs to use UnsafeCell<T> internally, have some code under the unsafe blocks. But they provide a safe API to the callers. A simple way to implement our own SimpleRefCell<T> would be as follows. It stores the value of type T in an UnsafeCell and also maintains a simple state to enforce the borrow checking principles at runtime. <pre> use std::cell::UnsafeCell; // Simplified version of RefCell to understand the internals // The real RefCell is more complex but follows this pattern #[derive(Copy, Clone)] enum BorrowFlag { Unused, // No borrows Reading(usize), // N shared borrows (count of readers) Writing, // 1 exclusive borrow } pub struct SimpleRefCell<T> { // UnsafeCell is the ONLY primitive that allows interior mutability // It's the magic ingredient that opts out of compile-time aliasing rules value: UnsafeCell<T>, // Runtime borrow tracking - this is the key! borrow_flag: UnsafeCell<BorrowFlag>, } // Key insight: RefCell itself doesn't need to be mut to provide mut access impl<T> SimpleRefCell<T> { pub fn new(value: T) -> Self { SimpleRefCell { value: UnsafeCell::new(value), borrow_flag: UnsafeCell::new(BorrowFlag::Unused), } } // Takes &self (shared reference) but returns mutable access! pub fn borrow_mut(&self) -> Result<&mut T, &'static str> { unsafe { let flag = &mut *self.borrow_flag.get(); // Runtime check: ensure no other borrows exist match *flag { BorrowFlag::Unused => { // Safe to give mutable access *flag = BorrowFlag::Writing; Ok(&mut *self.value.get()) } _ => { // Already borrowed! Panic in real RefCell Err("Already borrowed!") } } } } pub fn borrow(&self) -> Result<&T, &'static str> { unsafe { let flag = &mut *self.borrow_flag.get(); match *flag { BorrowFlag::Unused => { *flag = BorrowFlag::Reading(1); Ok(&*self.value.get()) } BorrowFlag::Reading(n) => { // Multiple readers are OK *flag = BorrowFlag::Reading(n + 1); Ok(&*self.value.get()) } BorrowFlag::Writing => { Err("Already mutably borrowed!") } } } } } </pre> Conclusion Interior mutability allows you to mutate the interiors (contents) of a data structure even when the exterior (container) is immutable. This is achieved by moving borrow checking from compile-time to runtime. RefCell<T> is one implementation that provides this pattern. Safety RefCell is safe because it enforces Rust’s borrowing rules at runtime. Panics if borrowing rules are violated (better than undefined behavior) Use try_borrow() and try_borrow_mut() to avoid panic, but needs error handling by the user. Only works in single-threaded contexts (not Send or Sync) Performance Small runtime overhead for borrow checking as it checks current borrow state on each borrow() or borrow_mut(). Generally negligible unless in very hot code paths Alternatives For multi-threading: Use Arc<Mutex<T>> or Arc<RwLock<T>>. For simple cases: Consider restructuring to avoid interior mutability For performance-critical code: Unsafe code with proper synchronization Here’s a text-based diagram summarizing the common Rust patterns involving interior mutability and shared ownership: <pre> +------------------+ +------------------+ +-----------------------+ | Immutable | | Shared Ownership | | Interior Mutability | | Ownership | | (Single-threaded)| | (Runtime borrow check)| +------------------+ +------------------+ +-----------------------+ | | | v v v let x = T; let x = Rc<T>; let x = Rc<RefCell<T>>; | | | | | | | | | | | | v v v No mutation No mutation ✅ Mutation allowed after borrow after borrow ❗ Panics on rule violation (e.g., double mutable borrow) +------------------+ +------------------+ +------------------+ | Multi-threaded | | Testing Patterns | | Unsafe Internals | | Shared Ownership | | with Mutability | | of RefCell | +------------------+ +------------------+ +------------------+ | | | v v v let x = Arc<Mutex<T>>; let mock = RefCell<Mock>; RefCell<T> uses UnsafeCell<T> | | | | | | v v v ✅ Thread-safe ✅ Mutable access ✅ Safe abstraction ✅ Mutation allowed ✅ Useful for mocking ❗ UnsafeCell is unsafe ❗ Locking overhead ❗ Panics possible ❗ Use with care </pre> Read the full article here: https://medium.com/nagarjuna-reddy/interior-mutability-in-rust-fad21a1f9ca8
Summary:
Please note that all contributions to JOHNWICK may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
JOHNWICK:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Search
Search
Editing
Interior Mutability in Rust
Add topic