mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-04-20 15:08:02 +02:00
Split interior mutability, mention OnceCell/OnceLock (#2573)
These types are really only useful as a static or in a user-defined type, neither of which are covered at this point.
This commit is contained in:
parent
5f7e0c3f64
commit
8121e7de7c
@ -156,6 +156,8 @@
|
||||
- [Borrow Checking](borrowing/borrowck.md)
|
||||
- [Borrow Errors](borrowing/examples.md)
|
||||
- [Interior Mutability](borrowing/interior-mutability.md)
|
||||
- [`Cell`](borrowing/interior-mutability/cell.md)
|
||||
- [`RefCell`](borrowing/interior-mutability/refcell.md)
|
||||
- [Exercise: Health Statistics](borrowing/exercise.md)
|
||||
- [Solution](borrowing/solution.md)
|
||||
- [Lifetimes](lifetimes.md)
|
||||
|
@ -12,77 +12,10 @@ The "interior mutability" pattern allows exclusive (mutable) access behind a
|
||||
shared reference. The standard library provides several ways to do this, all
|
||||
while still ensuring safety, typically by performing a runtime check.
|
||||
|
||||
## `Cell`
|
||||
|
||||
`Cell` wraps a value and allows getting or setting the value using only a shared
|
||||
reference to the `Cell`. However, it does not allow any references to the inner
|
||||
value. Since there are no references, borrowing rules cannot be broken.
|
||||
|
||||
```rust,editable
|
||||
use std::cell::Cell;
|
||||
|
||||
fn main() {
|
||||
// Note that `cell` is NOT declared as mutable.
|
||||
let cell = Cell::new(5);
|
||||
|
||||
cell.set(123);
|
||||
println!("{}", cell.get());
|
||||
}
|
||||
```
|
||||
|
||||
## `RefCell`
|
||||
|
||||
`RefCell` allows accessing and mutating a wrapped value by providing alternative
|
||||
types `Ref` and `RefMut` that emulate `&T`/`&mut T` without actually being Rust
|
||||
references.
|
||||
|
||||
These types perform dynamic checks using a counter in the `RefCell` to prevent
|
||||
existence of a `RefMut` alongside another `Ref`/`RefMut`.
|
||||
|
||||
By implementing `Deref` (and `DerefMut` for `RefMut`), these types allow calling
|
||||
methods on the inner value without allowing references to escape.
|
||||
|
||||
```rust,editable
|
||||
use std::cell::RefCell;
|
||||
|
||||
fn main() {
|
||||
// Note that `cell` is NOT declared as mutable.
|
||||
let cell = RefCell::new(5);
|
||||
|
||||
{
|
||||
let mut cell_ref = cell.borrow_mut();
|
||||
*cell_ref = 123;
|
||||
|
||||
// This triggers an error at runtime.
|
||||
// let other = cell.borrow();
|
||||
// println!("{}", *other);
|
||||
}
|
||||
|
||||
println!("{cell:?}");
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
The main thing to take away from this slide is that Rust provides _safe_ ways to
|
||||
modify data behind a shared reference. There are a variety of ways to ensure
|
||||
that safety, and `RefCell` and `Cell` are two of them.
|
||||
|
||||
- `RefCell` enforces Rust's usual borrowing rules (either multiple shared
|
||||
references or a single exclusive reference) with a runtime check. In this
|
||||
case, all borrows are very short and never overlap, so the checks always
|
||||
succeed.
|
||||
|
||||
- The extra block in the `RefCell` example is to end the borrow created by the
|
||||
call to `borrow_mut` before we print the cell. Trying to print a borrowed
|
||||
`RefCell` just shows the message `"{borrowed}"`.
|
||||
|
||||
- `Cell` is a simpler means to ensure safety: it has a `set` method that takes
|
||||
`&self`. This needs no runtime check, but requires moving values, which can
|
||||
have its own cost.
|
||||
|
||||
- Both `RefCell` and `Cell` are `!Sync`, which means `&RefCell` and `&Cell`
|
||||
can't be passed between threads. This prevents two threads trying to access
|
||||
the cell at once.
|
||||
that safety, and the next sub-slides present a few of them.
|
||||
|
||||
</details>
|
||||
|
25
src/borrowing/interior-mutability/cell.md
Normal file
25
src/borrowing/interior-mutability/cell.md
Normal file
@ -0,0 +1,25 @@
|
||||
# `Cell`
|
||||
|
||||
`Cell` wraps a value and allows getting or setting the value using only a shared
|
||||
reference to the `Cell`. However, it does not allow any references to the inner
|
||||
value. Since there are no references, borrowing rules cannot be broken.
|
||||
|
||||
```rust,editable
|
||||
use std::cell::Cell;
|
||||
|
||||
fn main() {
|
||||
// Note that `cell` is NOT declared as mutable.
|
||||
let cell = Cell::new(5);
|
||||
|
||||
cell.set(123);
|
||||
println!("{}", cell.get());
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
- `Cell` is a simple means to ensure safety: it has a `set` method that takes
|
||||
`&self`. This needs no runtime check, but requires moving values, which can
|
||||
have its own cost.
|
||||
|
||||
</details>
|
50
src/borrowing/interior-mutability/refcell.md
Normal file
50
src/borrowing/interior-mutability/refcell.md
Normal file
@ -0,0 +1,50 @@
|
||||
# `RefCell`
|
||||
|
||||
`RefCell` allows accessing and mutating a wrapped value by providing alternative
|
||||
types `Ref` and `RefMut` that emulate `&T`/`&mut T` without actually being Rust
|
||||
references.
|
||||
|
||||
These types perform dynamic checks using a counter in the `RefCell` to prevent
|
||||
existence of a `RefMut` alongside another `Ref`/`RefMut`.
|
||||
|
||||
By implementing `Deref` (and `DerefMut` for `RefMut`), these types allow calling
|
||||
methods on the inner value without allowing references to escape.
|
||||
|
||||
```rust,editable
|
||||
use std::cell::RefCell;
|
||||
|
||||
fn main() {
|
||||
// Note that `cell` is NOT declared as mutable.
|
||||
let cell = RefCell::new(5);
|
||||
|
||||
{
|
||||
let mut cell_ref = cell.borrow_mut();
|
||||
*cell_ref = 123;
|
||||
|
||||
// This triggers an error at runtime.
|
||||
// let other = cell.borrow();
|
||||
// println!("{}", *other);
|
||||
}
|
||||
|
||||
println!("{cell:?}");
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
- `RefCell` enforces Rust's usual borrowing rules (either multiple shared
|
||||
references or a single exclusive reference) with a runtime check. In this
|
||||
case, all borrows are very short and never overlap, so the checks always
|
||||
succeed.
|
||||
|
||||
- The extra block in the example is to end the borrow created by the call to
|
||||
`borrow_mut` before we print the cell. Trying to print a borrowed `RefCell`
|
||||
just shows the message `"{borrowed}"`.
|
||||
|
||||
## More to Explore
|
||||
|
||||
There are also `OnceCell` and `OnceLock`, which allow initialization on first
|
||||
use. Making these useful requires some more knowledge than students have at this
|
||||
time.
|
||||
|
||||
</details>
|
@ -34,6 +34,9 @@ Interior mutability is possible through a
|
||||
[`Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html), atomic or
|
||||
similar.
|
||||
|
||||
It is common to use `OnceLock` in a static as a way to support initialization on
|
||||
first use. `OnceCell` is not `Sync` and thus cannot be used in this context.
|
||||
|
||||
Thread-local data can be created with the macro `std::thread_local`.
|
||||
|
||||
</details>
|
||||
|
Loading…
x
Reference in New Issue
Block a user