partway through ch15.5
Some checks failed
Test Gitea Actions / first (push) Successful in 13s
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

This commit is contained in:
darkicewolf50 2025-03-06 16:52:47 -07:00
parent 90ffa5d8a5
commit 94359e3975
3 changed files with 364 additions and 5 deletions

View File

@ -17,6 +17,6 @@
"repelStrength": 10, "repelStrength": 10,
"linkStrength": 1, "linkStrength": 1,
"linkDistance": 250, "linkDistance": 250,
"scale": 1.0000000000000013, "scale": 0.8410178518902366,
"close": false "close": false
} }

View File

@ -35,6 +35,20 @@
"title": "Smart Pointers" "title": "Smart Pointers"
} }
}, },
{
"id": "74da570aae42b3e7",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Ref Cell Mutability.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Ref Cell Mutability"
}
},
{ {
"id": "7615e7dc6a30893a", "id": "7615e7dc6a30893a",
"type": "leaf", "type": "leaf",
@ -60,7 +74,7 @@
} }
} }
], ],
"currentTab": 3 "currentTab": 2
} }
], ],
"direction": "vertical" "direction": "vertical"
@ -203,10 +217,11 @@
"command-palette:Open command palette": false "command-palette:Open command palette": false
} }
}, },
"active": "6ed9f182aa3d5f3e", "active": "74da570aae42b3e7",
"lastOpenFiles": [ "lastOpenFiles": [
"Reference Counter Smart Pointer.md",
"Smart Pointers.md", "Smart Pointers.md",
"Test Controls.md",
"Reference Counter Smart Pointer.md",
"Ref Cell Mutability.md", "Ref Cell Mutability.md",
"The Performance Closures and Iterators.md", "The Performance Closures and Iterators.md",
"Improving The IO Project.md", "Improving The IO Project.md",
@ -217,7 +232,6 @@
"Writing_Tests.md", "Writing_Tests.md",
"minigrep/src/lib.rs", "minigrep/src/lib.rs",
"Test_Organization.md", "Test_Organization.md",
"Test Controls.md",
"Traits.md", "Traits.md",
"Modules and Use.md", "Modules and Use.md",
"Modules.md", "Modules.md",

View File

@ -1 +1,346 @@
# `RefCell<T>` and the Interior Mutability Pattern # `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>`