mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 13:04:18 -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"
|
"direction": "vertical"
|
||||||
@ -217,12 +217,12 @@
|
|||||||
"command-palette:Open command palette": false
|
"command-palette:Open command palette": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"active": "74da570aae42b3e7",
|
"active": "6ed9f182aa3d5f3e",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
|
"Ref Cell Mutability.md",
|
||||||
"Smart Pointers.md",
|
"Smart Pointers.md",
|
||||||
"Test Controls.md",
|
"Test Controls.md",
|
||||||
"Reference Counter Smart Pointer.md",
|
"Reference Counter Smart Pointer.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",
|
||||||
"Tests.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.
|
Now we will dig into how it works.
|
||||||
|
|
||||||
### Keeping Track of Borrows at Runtime with `RefCell<T>`
|
### 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.
|
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