Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

15.6 RefCell and Interior Mutability - Escaping Safety Restrictions

15.6.1 What Is Interior Mutability

Interior mutability is one of Rust’s design patterns. It allows programmers to modify data while only holding immutable references.

Usually, such behavior would be forbidden by the borrow rules (see 4.4. Reference and Borrowing), but in order to change data, the interior mutability pattern uses unsafe code inside the data structure to bypass Rust’s normal mutability and borrowing rules.

Unsafe code tells the compiler that we are checking the rules ourselves instead of relying on the compiler to do it for us. Concepts related to unsafe code will be covered in later articles.

15.6.2 RefCell<T>

Unlike Rc<T>, RefCell<T> represents the unique ownership of the data it holds.

To understand the difference between RefCell<T> and Box<T>, we need to revisit the borrow rules (see 4.4. Reference and Borrowing):

  • At any given time, you can have either one mutable reference or any number of immutable references, but not both.
  • References are always valid.

PS: “At any given time” can be understood as “within a given scope.”

The difference between RefCell<T> and Box<T> is as follows:

TypeCheck StageResult of Violating the Rules
Box<T>Borrow rules checked at compile timeCompile-time error
RefCell<T>Borrow rules checked at runtimeTriggers panic

Checking borrow rules at different stages has different characteristics:

  • Compile time:

    • Problems are exposed as early as possible
    • There is no runtime overhead
    • It is the best choice for most scenarios
    • It is Rust’s default behavior
  • Runtime:

    • Problems are exposed later, possibly even in production
    • There is a small performance cost due to borrow tracking
    • It enables certain memory-safe scenarios, such as modifying data inside an immutable environment

When Should You Use RefCell<T>?

The Rust compiler checks all code at compile time. It can understand most code; if there is no problem, compilation succeeds, and if there is a problem, it reports an error.

The Rust compiler is very conservative. Some code cannot be fully analyzed at compile time, and Rust will reject such code directly, even if it is actually correct.

Rust is this conservative to guarantee safety. Although rejecting perfectly valid code can be inconvenient for developers, it avoids catastrophic consequences.

If the compiler cannot analyze a piece of code but the developer can guarantee that the code satisfies the borrow rules, then RefCell<T> is a good choice.

Like RefCell<T>, Rc<T> is only suitable for single-threaded scenarios.

15.6.3 How to Choose Between Box<T>, Rc<T>, and RefCell<T>

You can choose among the three based on the characteristics listed in the table below:

FeatureBox<T>Rc<T>RefCell<T>
Ownership of the same dataOne ownerMultiple ownersOne owner
Mutability / borrow checkingMutable and immutable borrows (compile-time checks)Immutable borrows (compile-time checks)Mutable and immutable borrows (runtime checks)

One extra note: because RefCell<T> is checked only at runtime, even though RefCell<T> itself is immutable, we can still modify the value stored inside it.

15.6.4 Interior Mutability: Mutably Borrowing an Immutable Value

This title is a little tangled. It means using an &mut reference on a type that has not been declared as mut. An example makes it clear:

fn main() {
    let x = 5;
    let y = &mut x;
}

One implication of the borrow rules is that when you have an immutable value, you cannot mutably borrow it. So this code produces an error:

error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
 --> src/main.rs:3:13
  |
3 |     let y = &mut x;
  |             ^^^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
2 |     let mut x = 5;
  |         +++

For more information about this error, try `rustc --explain E0596`.
error: could not compile `borrowing` (bin "borrowing") due to 1 previous error

However, in some specific situations, we need a value that remains immutable to the outside, but can modify itself inside its methods, and no code other than the value’s own methods can modify it. That is called interior mutability. RefCell<T> exists for exactly this situation.

But RefCell<T> does not completely bypass the borrow rules. Although the compile-time checks pass, violating the borrow rules at runtime will cause the program to panic.

Now let’s look at an example (lib.rs):

Function: track how close a value is to a maximum, and issue a warning when the value reaches a certain level

#![allow(unused)]
fn main() {
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!");
        }
    }
}
}

The logic of this example is not important; let’s look at the structure:

  • At the top of the program, we define the Messenger trait, which contains the signature of the send method: it takes &self and a string slice parameter msg of type &str.

  • Below that, we define a struct called LimitTracker. It is a generic type with lifetime 'a and generic parameter T, where T must live for 'a and implement the Messenger trait defined at the top of the program. LimitTracker has three fields:

    • messenger: of type &'a T
    • value: of type usize
    • max: of type usize
  • Next, an impl block defines the associated function new on LimitTracker. Its parameters are messenger of type &T and max of type usize, and it returns a LimitTracker. This function creates a LimitTracker instance whose:

    • messenger field is the value of the messenger parameter
    • value field is 0
    • max field is the value of the max parameter
  • LimitTracker also has a method called set_value. Its first parameter is a mutable reference to self, &mut self, and its second parameter is value, of type usize. The logic inside the method is simple. It divides the self.value field by the self.max field (converting both to f64 to avoid losing precision) to get a percentage, stores it in percentage_of_max, and uses the send method from the Messenger trait to send different warnings depending on the size of percentage_of_max.

Using Test Doubles for Testing

There is a problem here. If we want to test this set_value method, we need the method to output something that we can assert against. But set_value actually returns nothing, so it does not provide any result for assertions.

What we want to test is that when we create a LimitTracker instance with a value that implements the Messenger trait and a max value, passing in different values will trigger Messenger to send different messages.

To solve this problem, let’s introduce a test double, which in Chinese is called a testing substitute. It is a general testing concept that represents a replacement used in tests. Among test doubles, there is a specific type called a Mock Object, which is responsible for recording what happens during the test. We can use those records to assert whether the test ran correctly.

Rust does not have a built-in equivalent, and the standard library does not provide mock objects, but we can define our own struct to do the same thing.

Let’s continue with the code above:

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    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));
        }
    }

    #[test]
    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);
    }
}
}
  • At the beginning of the test module, we declare a MockMessenger struct with one field, sent_messages, which stores sent messages and has type Vec<String>.

  • MockMessenger then gets a new function through an impl block to create instances of MockMessenger. The sent_messages field is initialized with an empty Vector.

  • Next, we implement the Messenger trait defined at the top of the code for MockMessenger. Once this trait is implemented, MockMessenger can be used to create a LimitTracker because LimitTracker requires its generic type to implement Messenger. When the send method is called, the message is stored in the sent_messages Vector on MockMessenger.

  • Finally, the it_sends_an_over_75_percent_warning_message test function tests the over-75-percent case. First, it creates a MockMessenger instance called mock_messenger, then creates a LimitTracker instance called limit_tracker, and then calls a method on the LimitTracker instance. Finally, it asserts by checking the number of elements in mock_messenger.sent_messages.

At this point, the code logic has a problem, and running it will produce an error:

error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference
  --> src/lib.rs:58:13
   |
58 |             self.sent_messages.push(String::from(message));
   |             ^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so it cannot be borrowed as mutable
   |
help: consider changing this to be a mutable reference in the `impl` method and the `trait` definition
   |
 2 ~     fn send(&mut self, msg: &str);
 3 | }
...
56 |     impl Messenger for MockMessenger {
57 ~         fn send(&mut self, message: &str) {
   |

For more information about this error, try `rustc --explain E0596`.
error: could not compile `limit-tracker` (lib test) due to 1 previous error

The error occurs in the send method implementation for MockMessenger:

#![allow(unused)]
fn main() {
impl Messenger for MockMessenger {
    fn send(&self, message: &str) {
        self.sent_messages.push(String::from(message));
    }
}
}

We cannot modify MockMessenger to track messages, because the send method’s signature takes an immutable reference to self. We also cannot replace it with &mut self, because then the signature of send would not match the signature defined in the Messenger trait, which uses &self.

For this kind of situation that needs interior mutability, we can use RefCell<T>. We only need to wrap the sent_messages field of MockMessenger in RefCell<T>:

#![allow(unused)]
fn main() {
struct MockMessenger {
    sent_messages: RefCell<Vec<String>>,
}
}

Because RefCell<T> is not in the prelude, we need to bring it into scope before using it:

#![allow(unused)]
fn main() {
use std::cell::RefCell;
}

After making that change, every piece of code that uses the sent_messages field needs to wrap it with RefCell<T> as well:

#![allow(unused)]
fn main() {
impl MockMessenger {
    fn new() -> MockMessenger {
        MockMessenger {
            sent_messages: RefCell::new(vec![]),
        }
    }
}
}

How do we use RefCell? Data created with RefCell can be modified with the borrow_mut method. Calling borrow_mut on the argument gives us a mutable reference, so the send method for MockMessenger can use borrow_mut:

#![allow(unused)]
fn main() {
impl Messenger for MockMessenger {
    fn send(&self, message: &str) {
        self.sent_messages.borrow_mut().push(String::from(message));
    }
}
}

This way, even though the send parameter is an immutable reference, the value can still be modified inside the function body through borrow_mut.

Finally, change the assertion in the test function:

#![allow(unused)]
fn main() {
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.borrow().len(), 1);
}
}

Using borrow on mock_messenger lets us obtain an immutable reference to the value for the assertion.

Now the code works without any issues. The full code is:

#![allow(unused)]
fn main() {
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!");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;

    struct MockMessenger {
        sent_messages: RefCell<Vec<String>>,
    }

    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger {
                sent_messages: RefCell::new(vec![]),
            }
        }
    }

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            self.sent_messages.borrow_mut().push(String::from(message));
        }
    }

    #[test]
    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.borrow().len(), 1);
    }
}
}

15.6.5 Recording Borrow Information at Runtime with RefCell<T>

In fact, the borrow_mut and borrow methods used above are like two safe interfaces provided to users:

  • borrow returns the smart pointer Ref<T>, which implements the Deref trait
  • borrow_mut returns the smart pointer RefMut<T>, which implements the Deref and DerefMut traits

RefCell<T> keeps track of how many active Ref<T> and RefMut<T> values exist:

  • Each time borrow is called, the immutable borrow count increases by 1. When any Ref<T> value goes out of scope and is dropped, the immutable borrow count decreases by 1.
  • Each time borrow_mut is called, the mutable borrow count increases by 1. When any RefMut<T> value goes out of scope and is dropped, the mutable borrow count decreases by 1.

Just like the compile-time borrow rules (see 4.4. Reference and Borrowing), RefCell<T> allows us to have many immutable borrows or one mutable borrow at any point in time.

If we try to violate these rules, the implementation of RefCell<T> will panic at runtime because RefCell<T> checks the borrow rules at runtime. The panic already borrowed: BorrowMutError is how RefCell<T> handles borrow-rule violations at runtime.

15.6.6 An Example Combining Rc<T> and RefCell<T>

Rc<T> allows some data to be owned by multiple owners, but it only provides immutable access to that data. If you have an Rc<T> that contains a RefCell<T>, you can get a value that has multiple owners and is mutable.

Now let’s look at mutable data with multiple ownership by combining Rc<T> and RefCell<T>:

#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let value = Rc::new(RefCell::new(5));

    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

    let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
    let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));

    *value.borrow_mut() += 10;

    println!("a after = {a:?}");
    println!("b after = {b:?}");
    println!("c after = {c:?}");
}

Do you remember the Cons list example from the previous article? We used Rc<T> to allow multiple lists to share ownership of another list. Because Rc<T> only stores immutable values, once any value in the list is created, we cannot change it. With what we learned in this article, let’s add RefCell<T> so we can change values in the list:

  • First, when defining the List enum, wrap the i32 associated with Cons in RefCell<> so the value can be mutated. Then wrap that RefCell in Rc<> so multiple owners can share it, and keep the rest unchanged.
  • Remember to import Rc and RefCell into the current scope.
  • Then create the instances with Rc::new() and RefCell::new(). a uses Rc::clone() to share the value of value, and b and c use Rc::clone() to share the value of a (provided that a is wrapped in Rc<>).
  • Finally, use borrow_mut on RefCell<T> to obtain a mutable borrow of value. Dereferencing with * lets us treat it like an i32 so that we can add 10.

Output:

a after = Cons(RefCell { value: 15 }, Nil)
b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))

As expected, everything works fine.

15.6.7 Other Types That Can Implement Interior Mutability

  • Cell<T>: accesses data by copying it
  • Mutex<T>: used to implement interior mutability in a multi-threaded context