diff --git a/.obsidian/graph.json b/.obsidian/graph.json index a8eb83a..508340a 100644 --- a/.obsidian/graph.json +++ b/.obsidian/graph.json @@ -17,6 +17,6 @@ "repelStrength": 10, "linkStrength": 1, "linkDistance": 250, - "scale": 1.0000000000000013, + "scale": 0.8410178518902366, "close": false } \ No newline at end of file diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 70a8544..4f5c9c8 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -35,6 +35,20 @@ "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", "type": "leaf", @@ -60,7 +74,7 @@ } } ], - "currentTab": 3 + "currentTab": 2 } ], "direction": "vertical" @@ -203,10 +217,11 @@ "command-palette:Open command palette": false } }, - "active": "6ed9f182aa3d5f3e", + "active": "74da570aae42b3e7", "lastOpenFiles": [ - "Reference Counter Smart Pointer.md", "Smart Pointers.md", + "Test Controls.md", + "Reference Counter Smart Pointer.md", "Ref Cell Mutability.md", "The Performance Closures and Iterators.md", "Improving The IO Project.md", @@ -217,7 +232,6 @@ "Writing_Tests.md", "minigrep/src/lib.rs", "Test_Organization.md", - "Test Controls.md", "Traits.md", "Modules and Use.md", "Modules.md", diff --git a/Ref Cell Mutability.md b/Ref Cell Mutability.md index c25255f..964af12 100644 --- a/Ref Cell Mutability.md +++ b/Ref Cell Mutability.md @@ -1 +1,346 @@ # `RefCell` 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`. + +We will be exploring this structure. + +## Enforcing Borrowing Rules at Runtime with `RefCell` +Unlike `Rc`, `RefCell` type represents single ownership over the data it holds. + +This makes `RefCell` similar to `Box` 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` and references, the borrowing rules' invariants are enforced at compile time. + +With `RefCell`, these invariants are enforced *at runtime*. + +With references if you break the borrowing rules, you get a compiler error. + +With `RefCell` 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` type is useful when you are sure your code follows the borrowing rules, the compiler is unable to understand and guarantee that. + +`RefCell` is similar to `Rc` 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` in a multithreaded application. + +Here are the reasons to choose `Box`, `Rc`, or `RefCell` +- `Rc` enables multiple owners of the same data + - `Box` and `RefCell` has single owners +- `Box` allows immutable or mutable borrows checked at compile time + - `Rc` allows only immutable borrows checked at compile time + - `RefCell` allows immutable or mutable borrows checked at runtime +- Due to `RefCell` allowing mutable borrows to be checked at runtime, you are able to mutate the value inside the `RefCell` even when the `RefCell` 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` is one way to get the ability to have interior mutability. + +Another reminder that `RefCell` 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` 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, + } + + 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`, 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>, + } + + 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>` instead of using a `Vec`. + +Now in the `new` function we create a new empty vector of `RefCell>` 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>` in `self.sent_messages` to get a mutable reference to the vector inside the `RefCell>`. + +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>` to get an immutable reference to the vector. + +Now we will dig into how it works. + +### Keeping Track of Borrows at Runtime with `RefCell`