diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 5e17fa1a..218f69df 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -113,6 +113,7 @@ - [Recursive Data Types](std/box-recursive.md) - [Niche Optimization](std/box-niche.md) - [Rc](std/rc.md) + - [Cell/RefCell](std/cell.md) - [Modules](modules.md) - [Visibility](modules/visibility.md) - [Paths](modules/paths.md) diff --git a/src/std/cell.md b/src/std/cell.md new file mode 100644 index 00000000..0b2f4a4c --- /dev/null +++ b/src/std/cell.md @@ -0,0 +1,52 @@ +# `Cell` and `RefCell` + +[`Cell`][https://doc.rust-lang.org/std/cell/struct.Cell.html] and +[`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) implement +what Rust calls *interior mutability:* mutation of values in an immutable +context. + +`Cell` is typically used for simple types, as it requires copying or moving +values. More complex interior types typically use `RefCell`, which tracks shared +and exclusive references at runtime and panics if they are misused. + +```rust,editable +use std::cell::RefCell; +use std::rc::Rc; + +#[derive(Debug, Default)] +struct Node { + value: i64, + children: Vec>>, +} + +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 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()); +} +``` + +
+ +* If we were using `Cell` instead of `RefCell` in this example, we would have to move the `Node` out of the `Rc` to push children, then move it back in. This is safe because there's always one, un-referenced value in the cell, but it's not ergonomic. +* To do anything with a Node, you must call a `RefCell` method, usually `borrow` or `borrow_mut`. +* Demonstrate that reference loops can be created by adding `root` to `subtree.children` (don't try to print it!). +* 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/std/rc.md b/src/std/rc.md index 18ec5bcc..73e887e3 100644 --- a/src/std/rc.md +++ b/src/std/rc.md @@ -15,17 +15,14 @@ fn main() { } ``` -* If you need to mutate the data inside an `Rc`, you will need to wrap the data in - a type such as [`Cell` or `RefCell`][2]. -* See [`Arc`][3] and [`Mutex`][4] if you are in a multi-threaded context. -* You can *downgrade* a shared pointer into a [`Weak`][5] pointer to create cycles +* See [`Arc`][2] and [`Mutex`][3] if you are in a multi-threaded context. +* You can *downgrade* a shared pointer into a [`Weak`][4] pointer to create cycles that will get dropped. [1]: https://doc.rust-lang.org/std/rc/struct.Rc.html -[2]: https://doc.rust-lang.org/std/cell/index.html -[3]: ../concurrency/shared_state/arc.md -[4]: https://doc.rust-lang.org/std/sync/struct.Mutex.html -[5]: https://doc.rust-lang.org/std/rc/struct.Weak.html +[2]: ../concurrency/shared_state/arc.md +[3]: https://doc.rust-lang.org/std/sync/struct.Mutex.html +[4]: https://doc.rust-lang.org/std/rc/struct.Weak.html
@@ -34,37 +31,8 @@ fn main() { * `Rc::clone` is cheap: it creates a pointer to the same allocation and increases the reference count. Does not make a deep clone and can generally be ignored when looking for performance issues in code. * `make_mut` actually clones the inner value if necessary ("clone-on-write") and returns a mutable reference. * Use `Rc::strong_count` to check the reference count. -* Compare the different datatypes mentioned. `Box` enables (im)mutable borrows that are enforced at compile time. `RefCell` enables (im)mutable borrows that are enforced at run time and will panic if it fails at runtime. * `Rc::downgrade` gives you a *weakly reference-counted* object to create cycles that will be dropped properly (likely in combination with - `RefCell`). - -```rust,editable -use std::rc::{Rc, Weak}; -use std::cell::RefCell; - -#[derive(Debug)] -struct Node { - value: i64, - parent: Option>>, - children: Vec>>, -} - -fn main() { - let mut root = Rc::new(RefCell::new(Node { - value: 42, - parent: None, - children: vec![], - })); - let child = Rc::new(RefCell::new(Node { - value: 43, - children: vec![], - parent: Some(Rc::downgrade(&root)) - })); - root.borrow_mut().children.push(child); - - println!("graph: {root:#?}"); -} -``` + `RefCell`, on the next slide).