1
0
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:
Dustin J. Mitchell 2025-01-20 12:48:02 -05:00 committed by GitHub
parent 5f7e0c3f64
commit 8121e7de7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 81 additions and 68 deletions

View File

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

View File

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

View 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>

View 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>

View File

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