diff --git a/training-slides/src/SUMMARY.md b/training-slides/src/SUMMARY.md index 4a1c183..415cf04 100644 --- a/training-slides/src/SUMMARY.md +++ b/training-slides/src/SUMMARY.md @@ -28,7 +28,7 @@ Using Rust on Windows/macOS/Linux. Requires [Rust Fundamentals](#rust-fundamenta * [Lifetimes](./lifetimes.md) * [Cargo Workspaces](./cargo-workspaces.md) * [Heap Allocation (Box and Rc)](./heap.md) -* [Shared Mutability (Cell, RefCell)](./shared-mutability.md) +* [Shared Mutability (Cell, RefCell, OnceCell)](./shared-mutability.md) * [Thread Safety (Send/Sync, Arc, Mutex)](./thread-safety.md) * [Closures and the Fn/FnOnce/FnMut traits](./closures.md) * [Spawning Threads and Scoped Threads](./spawning-threads.md) diff --git a/training-slides/src/shared-mutability.md b/training-slides/src/shared-mutability.md index 2c3ae75..99479ce 100644 --- a/training-slides/src/shared-mutability.md +++ b/training-slides/src/shared-mutability.md @@ -179,6 +179,29 @@ impl Post { } ``` +Note: + +As an in-depth example of the borrowchecker's limitations, consider the [Splitting Borrows](https://doc.rust-lang.org/nomicon/borrow-splitting.html) idiom, which allows one to borrow different fields of the same struct with different mutability semantics: + +```rust +struct Foo { + a: i32, + b: i32, + c: i32, +} + +let mut x = Foo {a: 0, b: 0, c: 0}; +let a = &mut x.a; +let b = &mut x.b; +let c = &x.c; +*b += 1; +let c2 = &x.c; +*a += 10; +println!("{} {} {} {}", a, b, c, c2); +``` + +The code works, but you *have* to use shadowing with `let a = &mut x.a;` or else the compiler will error. The borrowchecker is particularly frail here - replacing `Foo` with `x = [1,2,3]` and trying to borrow indexes will make it error out. + ## `RefCell` A `RefCell` is also safe, but lets you *borrow* or *mutably borrow* the contents. @@ -252,3 +275,35 @@ To get *shared ownership* and *mutability* you need two things: * `Rc>` * (Multi-threaded programs might use `Arc>`) + +## `OnceCell` for special cases + +If you only need to modify a field *once*, a `OnceCell` can help you keep the ownership system checks at compile-time + +```rust +use std::time::Instant; + +fn main() { + let post = Post { + content: String::from("Blah"), + ..Post::default() + }; + assert!(post.first_viewed_at.get().is_none()); + println!("{:?}", post.date_of_first_view()); + assert!(post.first_viewed_at.get().is_some()); +} + +#[derive(Debug, Default)] +struct Post { + content: String, + first_viewed_at: std::cell::OnceCell, +} + +impl Post { + fn date_of_first_view(&self) -> Instant { + *self.first_viewed_at.get_or_init(Instant::now) + } +} +``` + +A [LazyCell](https://doc.rust-lang.org/std/cell/struct.LazyCell.html) will do this initialization lazily, and a [LazyLock](https://doc.rust-lang.org/std/sync/struct.LazyLock.html) will do it in a threadsafe way.