mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 13:04:18 -06:00
partway through ch15.5
This commit is contained in:
parent
90ffa5d8a5
commit
94359e3975
2
.obsidian/graph.json
vendored
2
.obsidian/graph.json
vendored
@ -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
|
||||||
}
|
}
|
22
.obsidian/workspace.json
vendored
22
.obsidian/workspace.json
vendored
@ -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",
|
||||||
|
@ -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>`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user