From 8121e7de7cc3091b0d292eb02da8c6209d31b079 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 20 Jan 2025 12:48:02 -0500 Subject: [PATCH] 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. --- src/SUMMARY.md | 2 + src/borrowing/interior-mutability.md | 69 +------------------- src/borrowing/interior-mutability/cell.md | 25 +++++++ src/borrowing/interior-mutability/refcell.md | 50 ++++++++++++++ src/user-defined-types/static.md | 3 + 5 files changed, 81 insertions(+), 68 deletions(-) create mode 100644 src/borrowing/interior-mutability/cell.md create mode 100644 src/borrowing/interior-mutability/refcell.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 9a94687c..815fa13a 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/borrowing/interior-mutability.md b/src/borrowing/interior-mutability.md index 118df400..b98a930a 100644 --- a/src/borrowing/interior-mutability.md +++ b/src/borrowing/interior-mutability.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:?}"); -} -``` -
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.
diff --git a/src/borrowing/interior-mutability/cell.md b/src/borrowing/interior-mutability/cell.md new file mode 100644 index 00000000..21d7c904 --- /dev/null +++ b/src/borrowing/interior-mutability/cell.md @@ -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()); +} +``` + +
+ +- `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. + +
diff --git a/src/borrowing/interior-mutability/refcell.md b/src/borrowing/interior-mutability/refcell.md new file mode 100644 index 00000000..d147015c --- /dev/null +++ b/src/borrowing/interior-mutability/refcell.md @@ -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:?}"); +} +``` + +
+ +- `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. + +
diff --git a/src/user-defined-types/static.md b/src/user-defined-types/static.md index e3fd9717..14d10e38 100644 --- a/src/user-defined-types/static.md +++ b/src/user-defined-types/static.md @@ -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`.