mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 04:54:17 -06:00
505 lines
21 KiB
Markdown
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. |