RustBrock/Ref Cell Mutability.md
darkicewolf50 fbc76348ad
Some checks failed
Test Gitea Actions / first (push) Successful in 17s
Test Gitea Actions / check-code (push) Failing after 13s
Test Gitea Actions / test (push) Has been skipped
Test Gitea Actions / documentation-check (push) Has been skipped
finished ch 15.5
2025-03-07 10:55:30 -07:00

505 lines
21 KiB
Markdown

# `RefCell<T>` and the Interior Mutability Pattern
*Interior mutability* is a design pattern in Rust that allows for you to mutate data even when there are immutable references to the data.
This is normally not allowed due to Rust's strict borrowing rules.
To mutate data, the pattern uses `unsafe` code inside a data structure to allow for this to happen even though Rust's usual rules that govern mutation and borrowing.
Unsafe will be covered later.
Unsafe code indicates that we are checking the rules manually and not the compiler , this is instead of relying on the compiler to check for us.
We can use types that use interior mutability pattern only when we can ensure that the borrowing rules will be followed at runtime.
This is despite the compiler being unable to ensure that the borrowing rules are followed.
One example of interior mutability pattern is the `RefCall<T>`.
We will be exploring this structure.
## Enforcing Borrowing Rules at Runtime with `RefCell<T>`
Unlike `Rc<T>`, `RefCell<T>` type represents single ownership over the data it holds.
This makes `RefCell<T>` similar to `Box<T>` but it has some differences.
Here is a recall of the borrowing rules so that we can then explain the difference between the two.
- At any given time, you can have *either* (not both) one mutable reference or nay number of immutable references.
- References must always be valid
With `Box<T>` and references, the borrowing rules' invariants are enforced at compile time.
With `RefCell<T>`, these invariants are enforced *at runtime*.
With references if you break the borrowing rules, you get a compiler error.
With `RefCell<T>` if you break the rules, your program will panic and exit.
The advantage of checking at compile time is that you will catch errors sooner in the development process and there is no impact to runtime performance.
This is due to all of the analysis is completed before.
This is why checking the borrow rules at compile time is the best choice in the majority of cases.
Hence why it is Rust's default.
Due to some of this analysis being impossible, the Rust compiler can't be sure that the code complies with the ownership rules, it can reject the program.
This would be a very conservative approach.
If Rust accepted a incorrect program, users wouldn't be able to trust in the guarantees Rusts makes.
Instead if Rust rejects a correct program, the programmer will be inconvenienced, but nothing drastic or catastrophic can occur.
`RefCell<T>` type is useful when you are sure your code follows the borrowing rules, the compiler is unable to understand and guarantee that.
`RefCell<T>` is similar to `Rc<T>` in regards to the fact that it is used in single-threaded cases.
It will give a compile-time error if you attempt to use it in a multithreaded cases.
We will discuss later how to get the same functionality of `RefCell<T>` in a multithreaded application.
Here are the reasons to choose `Box<T>`, `Rc<T>`, or `RefCell<T>`
- `Rc<T>` enables multiple owners of the same data
- `Box<T>` and `RefCell<T>` has single owners
- `Box<T>` allows immutable or mutable borrows checked at compile time
- `Rc<T>` allows only immutable borrows checked at compile time
- `RefCell<T>` allows immutable or mutable borrows checked at runtime
- Due to `RefCell<T>` allowing mutable borrows to be checked at runtime, you are able to mutate the value inside the `RefCell<T>` even when the `RefCell<T>` is immutable.
Mutating the value inside an immutable value is the *interior mutability* pattern.
Now we will look at a situation where interior mutability is useful and examine how it is possible.
## Interior Mutability: A Mutable Borrow to an Immutable Value
One consequence of the borrowing rules is that when you have an immutable value, you can't borrow it mutably.
One example of this is the code below it will not compile
```rust
fn main() {
let x = 5;
let y = &mut x;
}
```
You would get this compilation output for this type of error
```
$ cargo run
Compiling borrowing v0.1.0 (file:///projects/borrowing)
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
```
There are situations where it would be useful for a value to mutate itself in its methods but also appear immutable to other code.
Then using `RefCell<T>` is one way to get the ability to have interior mutability.
Another reminder that `RefCell<T>` doesn't get around the borrowing rules completely.
The borrow checker in the compiler allows for this interior mutability and the borrowing rules are checked at runtime instead.
If you violate those rules, you will get a `panic!` instead of a compilation error.
Next we will go though a practical example where we can use `RefCell<T>` to mutate an immutable and see its usefulness.
### A Use Case for Interior Mutability: Mock Objects
Sometimes during tests a programmer will use a type in place of another type.
In order to observe a particular behavior and assert it is implemented correctly.
This placeholder type is called a *test double*.
This is often thought of as a "stunt double" in filmmaking, where a person substitutes for an actor to a particularly tricky scene.
Test doubles stand in for other types when running tests.
*Mock objects* are specific types of test doubles that record what happens during a test so that you can assert that the correct action(s) took place.
Rust doesn't have objects in in the same vain as other languages have objects.
Rust doesn't have mock objects functionality built into the std library like other languages do.
You can create a struct that will serve the same purpose as a mock object.
In this case we will test.
We will create a library that tracks a value against a maximum value and sends messages based on how close to the maximum value the current value is.
The library could be used to keep track of a user's quota for the number of API calls they are allowed to make, as an example.
This library will only provide the functionality of tracking how close to the maximum a value is and what the messages should be at what times.
Applications that use the library will be expected to provide the mechanism for sending the messages.
The application could put a message in the application, send an email, send a text message, or something else.
The library doesn't need to know what kind of message it is.
All it needs is something that implements a trat we will provide called `Messenger`.
```rust
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!");
}
}
}
```
One important part to notice is that the `Messenger` trait has one method called `send`.
This takes an immutable reference to `self` and the text of the message.
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`
Then we can change what we pass in for the `value` parameter, but `set_value` doesn't return anything for us to make a assertion on.
We want to be able to say if we create a `LimitTracker` with something that implements the `Messenger` trait and a particular value for `max`, when we pass different numbers for `value`.
The messenger is told to send the appropriate messages.
We need a mock object.
Instead of sending an email or text message when `send` is called, we will only keep track of the messages it's told to send.
We can create a new instance of the mock, then create a `LimitTracker` that uses the mock, call `set_value` method on `LimitTracker` and finally check that the mock has the messages we expect.
Here is an example that was an attempt to implement a mock object to do just that, but the borrow checker will not allow for it.
```rust
#[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);
}
}
```
Here the test code defines a `MockMessenger` struct that has a `sent_messages` field with a `Vec` of `String` values to keep track of the sent messages.
We also define an associated function `new`, this makes it convenient ot create new `MockMessenger` values that start with an empty list of messages.
Then we implement the `Messenger` trait for `MockMessenger` so that we can give a `MockMessenger` to a `LimitTracker`.
In the definition of `send`, we take the message passed in as a message passed in as a parameter and store it in the `MockMessenger` list of `sent_messages`.
Now in the test we are testing what happens when the `LimitTracker` is told to set `value` to something that is more than 75 percent of the `max` value.
First we create a new `MockMessenger`, this will start with a empty list of messages.
We then create a `LimitTracker` and we give it a reference to the `MockMessenger` and a `max` value of 100.
We call the `set_value` method in the `LimitTracker` with a value of 80.
This is more than 75% of 100.
We finally assert that the list of messages that the `MockMessenger` is keeping track of should now have one message in it.
There is one problem with this test, here is the output from running the test
```
$ cargo test
Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
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 the data it refers to 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
```
Since we can't modify the `MockMessenger` to keep track of the messages.
Due to the `send` method taking in an immutable reference to `self`.
We also can't take the suggestion from the error text to use `&mut slef` in both the `impl` method and the `trait` def.
We do not want to change the `Messenger` trait only for the sake of testing.
Instead we should find a way to make our test code work without changing the existing design.
Interior mutability would be highly useful in this situation.
We will store the `sent_messages` within a `RefCell<T>`, then the `send` method will then be able to modify `sent_messages` the messages.
Here it wat it would look like with this new definition
```rust
#[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() {
// --snip--
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}
}
```
Now `sent_messages` is a type `RefCell<Vec<String>>` instead of using a `Vec<String>`.
Now in the `new` function we create a new empty vector of `RefCell<Vec<String>>` instance.
For the implementation of the `send` method, the first parameter is still an immutable borrow of `self`, this matches the trait definition.
We can call `borrow_mut` on the `RefCell<Vec<String>>` in `self.sent_messages` to get a mutable reference to the vector inside the `RefCell<Vec<String>>`.
Next we can call `push` on the mutable reference to the vector to keep track of the messages sent during the test.
The last change we need to make is in the assertion.
We need to see how many items are in the inner vector, we call `borrow` on the `RefCell<Vec<String>>` to get an immutable reference to the vector.
Now we will dig into how it works.
### Keeping Track of Borrows at Runtime with `RefCell<T>`
Creating immutable and mutable references, we use `&` and `&mut` syntax.
With `RefCell<T>`, we use `borrow` and `borrow_mut` methods.
These are part of the safe API that belongs to `RefCell<T>`.
The `borrow` method returns the smart pointer type `Ref<T>`.
The `borrow_mut` method returns the smart pointer type `RefMut<T>`.
Both types implement the `Deref`, so they can be treated like regular references.
`RefCell<T>` keeps track of how many `Ref<T>` and `RefMut<T>` smart pointers are currently active.
Every time we call `borrow`, the `RefCell<T>` increases its count of how many immutable borrows are active.
When a `Ref<T>` value/"reference" goes out of scope, the count of immutable borrows goes down by 1.
`RefCell<T>` uses the same rules are the borrowing rules, it allows you to have many immutable borrows or one mutable borrow at any point in time.
If we violate the rules, rather than getting a compiler error as we would with references, the implementation of `RefCell<T>` will panic at runtime.
This example shows a modification of the `send` implementation.
Here we are deliberately trying to create two mutable borrows active for the same scope to illustrate that `RefCell<T>` prevents us from doing this at runtime.
```rust
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
let mut one_borrow = self.sent_messages.borrow_mut();
let mut two_borrow = self.sent_messages.borrow_mut();
one_borrow.push(String::from(message));
two_borrow.push(String::from(message));
}
}
```
Here we create the variable `one_borrow` for the `RefMut<T>` smart pointer returned form `borrow_mut`.
We then create a different variable `two_borrow` in the same way as `one_borrow`.
This makes two mutable references in the same scope. **This is not Allowed**.
When we run the tests for our library, the code will compile without errors but the test will fail with this output.
```
$ cargo test
Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s
Running unittests src/lib.rs (target/debug/deps/limit_tracker-e599811fa246dbde)
running 1 test
test tests::it_sends_an_over_75_percent_warning_message ... FAILED
failures:
---- tests::it_sends_an_over_75_percent_warning_message stdout ----
thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at src/lib.rs:60:53:
already borrowed: BorrowMutError
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::it_sends_an_over_75_percent_warning_message
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
```
Note that the code panicked with the message `already borrowed: BorrowMutError`.
This is how `RefCell<T>` handles violation of the borrowing rules at runtime.
Choosing to catch borrowing violation errors at runtime rather than compile time, this means that you may potentially be finding mistakes in your code at a later point in development.
Even possibly not until your code was deployed to production.
Also your code would have a small runtime performance penalty of keeping track of the borrows at runtime rather than at compile time.
However the benefit of using `RefCell<T>` makes it possible to write a mock object that can modify itself to keep track of the messages it has seen while you are using it in a context where only immutable values are allowed.
`RefCell<T>` despite its trade-offs to get more functionality than regular references provide.
### Having Multiple Owners of Mutable Data by Combining `Rc<T>` and `RefCell<T>`
A common use of `RefCell<T>` is in combination with `Rc<T>`.
`Rc<T>` lets you have multiple owners of some data, but it only gives immutable access to that data.
If you have a `Rc<T>` that holds a `RefCell<T>`, you can get a value that can have multiple owners *and* that you can mutate.
For example recall the cons list form before where we used `Rc<T>` to allow multiple lists to share ownership of another list.
Due to `Rc<T>` only being able to hold immutable values, we are unable to change any of the values in the list once it has been created.
Now lets add a `RefCell<T>` to be able to change the values in the lists.
This is an example where using a `RefCell<T>` in the `Cons` definition we can modify the value stored in all of the lists.
```rust
#[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:?}");
}
```
Here we create a value that is an instance of `Rc<RefCell<i32>>` and it is stored in the variable `value` so that it can be accessed lasted.
Next we create a `List` in `a` with a `Cons` variant that holds `value`.
We need to code `value` so both `a` and `value` have ownership of the inner `5` rather than transferring ownership from `value` to `a` or having `a` borrow form value.
Then we wrap `a` in a `Rc<T>` so when we create the lists `b` and `c` then can both refer to `a`. (This is the same as the before example)
After we create `a`, `b`, and `c`, we want to add 10 to the value in `value.
We can do this by calling `borrow_mut` on `value`, which uses the automatic dereferencing feature that was discussed previously.
We use this to dereference the `Rc<T>` to the inner `RefCell<T>` value.
The `borrow_mut` method returns a `RefMut` smart pointer and we use the dereference operator on it and change the inner value.
Finally we print `a`, `b`, and `c` so that we can see that they all have the modified value of 15 rather than 5.
```
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.63s
Running `target/debug/cons-list`
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))
```
This is very neat and a very useful technique.
By using `RefCell<T>` we have an outwardly immutable `List` value.
We can then use the methods on `RefCell<T>` that provide access to its interior mutability so we can modify our data when we need to.
The runtime checks of the borrowing rules ensure the protection form data races and it is sometimes worth trading a bit of speed for this level of flexibility in our data structures.
Again Note that `RefCell<T>` does **NOT** work for multithreaded code.
`Mutex<T>` is the tread-safe version of `RefCell<T>`, this will be discussed in the next chapter.