From eae282ddc116d2ed76c2945945600f27d45e904a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 12 Jan 2024 22:13:59 +0000 Subject: [PATCH] go back to using RefCell --- src/borrowing/interior-mutability.md | 77 ++++++++++++++++----------- src/concurrency/shared_state/mutex.md | 3 +- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/borrowing/interior-mutability.md b/src/borrowing/interior-mutability.md index d0bbd82419b3..f8c58d610b9e 100644 --- a/src/borrowing/interior-mutability.md +++ b/src/borrowing/interior-mutability.md @@ -4,57 +4,72 @@ minutes: 10 # Interior Mutability -In some situations, exclusive (`mut`) access cannot be represented at compile -time. For example, data might be accessed from multiple threads. The "interior -mutability" pattern allows exclusive access behind a shared reference, using a -runtime check to enforce the borrowing rules. +In some situations, it's necessary to modify data behind a shared (read-only) +reference. For example, a shared data structure might have an internal cache, +and wish to update that cache from read-only methods. -## `Mutex` +The "interior mutability" pattern allows exclusive (mutable) access behind a +shared reference. The standard library provides several ways to do this, all +ensuring safety by performing a runtime check. -One standard type that supports interior mutability is `Mutex` (known as a lock -in some other languages). Many threads can have a shared reference to a `Mutex`, -and the lock operation performs a _runtime_ check that no other thread holds the -lock before granting access to the value inside. +## `RefCell` ```rust,editable -use std::sync::Mutex; +use std::cell::RefCell; +use std::rc::Rc; -fn handle_hit(hit_counter: &Mutex) { - let mut num_hits = hit_counter.lock().unwrap(); - *num_hits += 1; +#[derive(Debug, Default)] +struct Node { + value: i64, + children: Vec>>, } -fn current_hit_count(hit_counter: &Mutex) -> u32 { - *hit_counter.lock().unwrap() +impl Node { + fn new(value: i64) -> Rc> { + Rc::new(RefCell::new(Node { value, ..Node::default() })) + } + + fn sum(&self) -> i64 { + self.value + self.children.iter().map(|c| c.borrow().sum()).sum::() + } } fn main() { - let hit_counter = Mutex::new(0); - handle_hit(&hit_counter); - println!("{} hits", current_hit_count(&hit_counter)); + let root = Node::new(1); + root.borrow_mut().children.push(Node::new(5)); + let subtree = Node::new(10); + subtree.borrow_mut().children.push(Node::new(11)); + subtree.borrow_mut().children.push(Node::new(12)); + root.borrow_mut().children.push(subtree); + + println!("graph: {root:#?}"); + println!("graph sum: {}", root.borrow().sum()); } ``` -## `Cell` and `RefCell` +## `Cell` `Cell` wraps a value and allows getting or setting the value, even with a shared reference to the `Cell`. Howerver, it does not allow any references to the -value. If there are no references, then borrowing rules cannot be broken. +value. Since there are no references, borrowing rules cannot be broken. -The `RefCell` type also provides interior mutability. Its `borrow` method allows -multiple shared references to the data it contains, while its `borrow_mut` -allows a single exclusive reference. It checks the borrowing rules at runtime -and panics if they are violated. +
-Neither `Cell` nor `RefCell` can be shared between threads. +- `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. -
+- `Rc` only allows shared (read-only) access to its contents, since its purpose + is to allow (and count) many references. But we want to modify the value, so + we need interior mutability. -- While the fundamentals course doesn't cover concurrency, most students will - have seen a mutex or lock before, and understand that it checks for an - existing lock at runtime, which is the key to interior mutability. +- Demonstrate that reference loops can be created by adding `root` to + `subtree.children` (don't try to print it!). -- The cell types are more unusual, if simpler. `RefCell` is, in effect, a - `RWMutex` that panics instead of blocking. +- To demonstrate a runtime panic, add a `fn inc(&mut self)` that increments + `self.value` and calls the same method on its children. This will panic in the + presence of the reference loop, with + `thread 'main' panicked at 'already borrowed: BorrowMutError'`.
diff --git a/src/concurrency/shared_state/mutex.md b/src/concurrency/shared_state/mutex.md index a5914bf9a35a..f9a06284e47d 100644 --- a/src/concurrency/shared_state/mutex.md +++ b/src/concurrency/shared_state/mutex.md @@ -1,7 +1,8 @@ # `Mutex` [`Mutex`][1] ensures mutual exclusion _and_ allows mutable access to `T` -behind a read-only interface: +behind a read-only interface (another form of +[interior mutability](../../borrowing/interior-mutability)): ```rust,editable use std::sync::Mutex;