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 Checking](borrowing/borrowck.md)
|
||||||
- [Borrow Errors](borrowing/examples.md)
|
- [Borrow Errors](borrowing/examples.md)
|
||||||
- [Interior Mutability](borrowing/interior-mutability.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)
|
- [Exercise: Health Statistics](borrowing/exercise.md)
|
||||||
- [Solution](borrowing/solution.md)
|
- [Solution](borrowing/solution.md)
|
||||||
- [Lifetimes](lifetimes.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
|
shared reference. The standard library provides several ways to do this, all
|
||||||
while still ensuring safety, typically by performing a runtime check.
|
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>
|
<details>
|
||||||
|
|
||||||
The main thing to take away from this slide is that Rust provides _safe_ ways to
|
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
|
modify data behind a shared reference. There are a variety of ways to ensure
|
||||||
that safety, and `RefCell` and `Cell` are two of them.
|
that safety, and the next sub-slides present a few 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.
|
|
||||||
|
|
||||||
</details>
|
</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
|
[`Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html), atomic or
|
||||||
similar.
|
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`.
|
Thread-local data can be created with the macro `std::thread_local`.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user