finished ch 15.5
Some checks failed
Test Gitea Actions / first (push) Successful in 17s
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-07 10:55:30 -07:00
parent 94359e3975
commit fbc76348ad
4 changed files with 164 additions and 4 deletions

View File

@ -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",

View File

@ -0,0 +1 @@
# Reference Cycles Can Leak Memory

View File

@ -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<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.

View File

@ -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)