mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-14 20:44:17 -06:00
finished ch 15.5
This commit is contained in:
parent
94359e3975
commit
fbc76348ad
6
.obsidian/workspace.json
vendored
6
.obsidian/workspace.json
vendored
@ -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",
|
||||
|
1
Leaky Reference Cycles.md
Normal file
1
Leaky Reference Cycles.md
Normal file
@ -0,0 +1 @@
|
||||
# Reference Cycles Can Leak Memory
|
@ -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.
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user