diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 4f5c9c8..04dbf15 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -74,7 +74,7 @@ } } ], - "currentTab": 2 + "currentTab": 4 } ], "direction": "vertical" @@ -217,12 +217,12 @@ "command-palette:Open command palette": false } }, - "active": "74da570aae42b3e7", + "active": "6ed9f182aa3d5f3e", "lastOpenFiles": [ + "Ref Cell Mutability.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", "Tests.md", diff --git a/Leaky Reference Cycles.md b/Leaky Reference Cycles.md new file mode 100644 index 0000000..67209d6 --- /dev/null +++ b/Leaky Reference Cycles.md @@ -0,0 +1 @@ +# Reference Cycles Can Leak Memory \ No newline at end of file diff --git a/Ref Cell Mutability.md b/Ref Cell Mutability.md index 964af12..87bd9ff 100644 --- a/Ref Cell Mutability.md +++ b/Ref Cell Mutability.md @@ -344,3 +344,162 @@ We need to see how many items are in the inner vector, we call `borrow` on the ` Now we will dig into how it works. ### Keeping Track of Borrows at Runtime with `RefCell` +Creating immutable and mutable references, we use `&` and `&mut` syntax. + +With `RefCell`, we use `borrow` and `borrow_mut` methods. + +These are part of the safe API that belongs to `RefCell`. + +The `borrow` method returns the smart pointer type `Ref`. + +The `borrow_mut` method returns the smart pointer type `RefMut`. + +Both types implement the `Deref`, so they can be treated like regular references. + +`RefCell` keeps track of how many `Ref` and `RefMut` smart pointers are currently active. + +Every time we call `borrow`, the `RefCell` increases its count of how many immutable borrows are active. + +When a `Ref` value/"reference" goes out of scope, the count of immutable borrows goes down by 1. + +`RefCell` 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` 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` 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` 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` 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` 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` despite its trade-offs to get more functionality than regular references provide. + +### Having Multiple Owners of Mutable Data by Combining `Rc` and `RefCell` + +A common use of `RefCell` is in combination with `Rc`. + +`Rc` lets you have multiple owners of some data, but it only gives immutable access to that data. + +If you have a `Rc` that holds a `RefCell`, 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` to allow multiple lists to share ownership of another list. + +Due to `Rc` 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` to be able to change the values in the lists. + +This is an example where using a `RefCell` in the `Cons` definition we can modify the value stored in all of the lists. +```rust +#[derive(Debug)] +enum List { + Cons(Rc>, Rc), + 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>` 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` 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` to the inner `RefCell` 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` we have an outwardly immutable `List` value. + +We can then use the methods on `RefCell` 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` does **NOT** work for multithreaded code. + +`Mutex` is the tread-safe version of `RefCell`, this will be discussed in the next chapter. \ No newline at end of file diff --git a/Smart Pointers.md b/Smart Pointers.md index b0133f1..b9448d9 100644 --- a/Smart Pointers.md +++ b/Smart Pointers.md @@ -56,4 +56,4 @@ The ones we will cover are the most common smart pointers in the std library: In addition we will also cover *interior mutability* patter where an immutable type exposes an API for mutating an interior value. -As well discuss *reference cycles*; how they can leak memory and how to prevent them. +As well discuss *reference cycles*; how they can leak memory and how to prevent them. [Section Link Here](./Leaky%20Reference%20Cycles.md)