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

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

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 [`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>