Skip to content

Commit

Permalink
go back to using RefCell
Browse files Browse the repository at this point in the history
  • Loading branch information
djmitche committed Jan 12, 2024
1 parent a473dd3 commit eae282d
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 32 deletions.
77 changes: 46 additions & 31 deletions src/borrowing/interior-mutability.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<u32>) {
let mut num_hits = hit_counter.lock().unwrap();
*num_hits += 1;
#[derive(Debug, Default)]
struct Node {
value: i64,
children: Vec<Rc<RefCell<Node>>>,
}
fn current_hit_count(hit_counter: &Mutex<u32>) -> u32 {
*hit_counter.lock().unwrap()
impl Node {
fn new(value: i64) -> Rc<RefCell<Node>> {
Rc::new(RefCell::new(Node { value, ..Node::default() }))
}
fn sum(&self) -> i64 {
self.value + self.children.iter().map(|c| c.borrow().sum()).sum::<i64>()
}
}
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.
<details>

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.

<details>
- `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'`.

</details>
3 changes: 2 additions & 1 deletion src/concurrency/shared_state/mutex.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# `Mutex`

[`Mutex<T>`][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;
Expand Down

0 comments on commit eae282d

Please sign in to comment.