Skip to content

Commit cacc981

Browse files
committed
improve the raii chapter slides based on the new STYLE guidelines
1 parent 4a79ba4 commit cacc981

File tree

8 files changed

+75
-70
lines changed

8 files changed

+75
-70
lines changed

src/idiomatic/leveraging-the-type-system/raii.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ fn main() -> Result<(), std::io::Error> {
4040

4141
<details>
4242

43-
- This example shows how easy it is to forget releasing a file descriptor when
44-
managing it manually. The code as written does not call `file.close()`. Did
45-
anyone in the class notice?
43+
- Easy to miss: `file.close()` is never called. Ask the class if they noticed.
4644

4745
- To release the file descriptor correctly, `file.close()` must be called after
4846
the last use — and also in early-return paths in case of errors.
@@ -60,8 +58,8 @@ fn main() -> Result<(), std::io::Error> {
6058
```
6159

6260
- Note that `Drop::drop` cannot return errors. Any fallible logic must be
63-
handled internally or ignored. In the standard library, errors returned while
64-
closing an owned file descriptor during `Drop` are silently discarded:
61+
handled internally or ignored. In the standard library, errors during FD
62+
closure inside `Drop` are silently discarded. See the implementation:
6563
<https://doc.rust-lang.org/src/std/os/fd/owned.rs.html#169-196>
6664

6765
- When is `Drop::drop` called?
@@ -75,8 +73,8 @@ fn main() -> Result<(), std::io::Error> {
7573
In contrast, C++ runs destructors in the original scope even for moved-from
7674
values.
7775

78-
- Insert `panic!("oops")` at the start of `read_to_end()` to show that `drop()`
79-
still runs during unwinding.
76+
- Demo: insert `panic!("oops")` at the start of `read_to_end()` and run it.
77+
`drop()` still runs during unwinding.
8078

8179
### More to Explore
8280

src/idiomatic/leveraging-the-type-system/raii/drop_bomb.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,30 @@ fn main() -> io::Result<()> {
4545
<details>
4646

4747
- A drop bomb ensures that a value like `Transaction` cannot be silently dropped
48-
in an unfinished state. The destructor panics if neither `commit()` nor
49-
`rollback()` has been called.
48+
in an unfinished state. The destructor panics if the transaction has not been
49+
explicitly finalized (for example, with `commit()`).
5050

51-
- The finalizing operation (like `commit()` or `rollback()`) often consumes the
52-
object and thus prevents the user from handling a finalized object.
51+
- The finalizing operation (such as `commit()`) usually take `self` by value.
52+
This ensures that once the transaction is finalized, the original object can
53+
no longer be used.
5354

5455
- A common reason to use this pattern is when cleanup cannot be done in `Drop`,
5556
either because it is fallible or asynchronous.
5657

5758
- This pattern is appropriate even in public APIs. It can help users catch bugs
5859
early when they forget to explicitly finalize a transactional object.
5960

60-
- If a value can be safely cleaned up in `Drop`, consider falling back to that
61-
behavior in Release mode and panicking only in Debug. This decision should be
62-
made based on the guarantees your API provides.
61+
- If cleanup can safely happen in `Drop`, some APIs choose to panic only in
62+
debug builds. Whether this is appropriate depends on the guarantees your API
63+
must enforce.
6364

64-
- Panicking in Release builds is a valid choice if silent misuse could lead to
65-
serious correctness issues or security concerns.
65+
- Panicking in Release builds is reasonable when silent misuse would cause major
66+
correctness or security problems.
6667

6768
## More to explore
6869

69-
There are additional patterns related to this slide that could be explored.
70+
Several related patterns help enforce correct teardown or prevent accidental
71+
drops.
7072

7173
- The [`drop_bomb` crate](https://docs.rs/drop_bomb/latest/drop_bomb/): A small
7274
utility that panics if dropped unless explicitly defused with `.defuse()`.

src/idiomatic/leveraging-the-type-system/raii/drop_bomb_forget.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,12 @@ In the previous slide we saw that calling
4242
[`std::mem::forget`](https://doc.rust-lang.org/std/mem/fn.forget.html) prevents
4343
`Drop::drop` from ever running.
4444

45-
This lets us avoid using a runtime flag entirely: when the transaction is
46-
successfully committed, we can defuse the drop bomb by forgetting the value
47-
instead of letting its destructor run.
45+
Remember that `mem::forget` leaks the value. This is safe in Rust, but the
46+
memory will not be reclaimed.
47+
48+
However, this avoids needing a runtime flag: when the transaction is
49+
successfully committed, we can _defuse_ the drop bomb — meaning we prevent
50+
`Drop` from running — by calling `std::mem::forget` on the value instead of
51+
letting its destructor run.
4852

4953
</details>

src/idiomatic/leveraging-the-type-system/raii/drop_guards.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ impl Drop for MutexGuard<'_> {
3333

3434
<details>
3535

36-
- The example above shows a simplified `Mutex` and its associated guard. Even
37-
though it is not a production-ready implementation, it illustrates the core
38-
idea: the guard enforces exclusive access, and its `Drop` implementation is
39-
used to unlock it again when the guard goes out of scope or is manually
40-
dropped.
36+
- The example above shows a simplified `Mutex` and its associated guard.
37+
38+
- Even though it is not a production-ready implementation, it illustrates the
39+
core idea:
40+
41+
- the guard represents exclusive access,
42+
- and its `Drop` implementation releases the lock when it goes out of scope.
4143

4244
## More to Explore
4345

@@ -47,14 +49,16 @@ core idea of a drop guard, not to demonstrate a proper Rust mutex design.
4749

4850
For brevity, several features are omitted:
4951

50-
- Unlike the std `Mutex`, which owns its value, this version keeps the value
51-
next to the `Mutex` rather than inside it.
52-
- Ergonomic access via `Deref` and `DerefMut` on `MutexGuard`.
52+
- A real `Mutex<T>` stores the protected value inside the mutex.\
53+
This toy example omits the value entirely to focus only on the drop guard
54+
mechanism.
55+
- Ergonomic access via `Deref` and `DerefMut` on `MutexGuard` (letting the guard
56+
behave like a `&T` or `&mut T`).
5357
- A fully blocking `.lock()` method and a non blocking `try_lock` variant.
5458

5559
You can explore the
5660
[`Mutex` implementation in Rust’s std library](https://doc.rust-lang.org/std/sync/struct.Mutex.html)
57-
as an example of a production ready mutex. The
61+
as an example of a production-ready mutex. The
5862
[`Mutex` from the `parking_lot` crate](https://docs.rs/parking_lot/latest/parking_lot/type.Mutex.html)
5963
is another worthwhile reference.
6064

src/idiomatic/leveraging-the-type-system/raii/drop_option.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,31 @@ fn main() -> std::io::Result<()> {
5454
- At the same time we still want RAII semantics: if the user forgets to call
5555
`close()`, the handle must be cleaned up automatically in `Drop`.
5656

57-
- Wrapping the handle in an `Option` gives us both behaviors.\
58-
`close()` extracts the handle with `take()`, and `Drop` only runs cleanup if a
59-
handle is still present.
60-
61-
You can demonstrate this by removing the line where we call `.close()`.
57+
- Wrapping the handle in an `Option` gives us both behaviors. `close()` extracts
58+
the handle with `take()`, and `Drop` only runs cleanup if a handle is still
59+
present.
60+
61+
Demo: remove the `.close()` call and run the code — `Drop` now prints the
62+
automatic cleanup.
6263

6364
- The main downside is ergonomics. `Option` forces us to handle both the `Some`
64-
and `None` case even in places where, logically, `None` should be impossible.
65-
Rust’s type system cannot express that guarantee here, so we pay a small
66-
boilerplate cost.
65+
and `None` case even in places where, logically, `None` cannot occur. Rust’s
66+
type system cannot express that relationship between `File` and its `Handle`,
67+
so we handle both cases manually.
6768

6869
## More to explore
6970

7071
Instead of `Option` we could use
7172
[`ManuallyDrop`](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html),
72-
which suppresses automatic destruction and gives full manual control.\
73-
This approach requires `unsafe`, since `ManuallyDrop` provides no guarantees
74-
that the programmer uses it correctly.
73+
which suppresses automatic destruction by preventing Rust from calling `Drop`
74+
for the value; you must handle teardown yourself.
7575

7676
The [_scopeguard_ example](./scope_guard.md) on the previous slide shows how
7777
`ManuallyDrop` can replace `Option` to avoid handling `None` in places where the
7878
value should always exist.
7979

8080
In such designs we typically track the drop state with a separate flag next to
81-
the `ManuallyDrop<Handle>`, allowing us to express whether the handle has
82-
already been manually consumed.
81+
the `ManuallyDrop<Handle>`, which lets us track whether the handle has already
82+
been manually consumed.
8383

8484
</details>

src/idiomatic/leveraging-the-type-system/raii/drop_skipped.md

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,25 @@ fn main() {
4545
[`std::mem::forget`](https://doc.rust-lang.org/std/mem/fn.forget.html) call.
4646
What do you think will happen?
4747

48-
Forgetting a value intentionally _leaks_ it. Leaking is still memory safe, but
49-
it prevents the destructor from ever running, so `drop()` will _not_ be
50-
called.
48+
Forgetting a value intentionally _leaks_ it — the memory is never reclaimed,
49+
but this is still memory-safe in Rust. Since the value is never dropped, its
50+
destructor does not run.
5151

5252
[`Box::leak`](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.leak)
5353
is another example of intentional leaking, often used to create data that
5454
lives for the remainder of the process.
5555

56-
- Undo the leak and uncomment the `panic!` just below it. What do you expect
57-
now?
56+
- Remove the `mem::forget` call, then uncomment the `panic!` below it. What do
57+
you expect now?
5858

59-
The stack will still unwind and the destructors will still run, even when the
60-
panic originates in `main`.
59+
With the default `panic=unwind` setting, the stack still unwinds and
60+
destructors run, even when the panic starts in `main`.
6161

62-
- This is only true when compiling with the default `panic=unwind` config
63-
profile.
64-
65-
With
62+
- With
6663
[`panic=abort`](https://doc.rust-lang.org/cargo/reference/profiles.html#panic),
6764
no unwinding takes place.
6865

69-
- Finally, consider what happens when you also uncomment the `panic!` inside
70-
`Foo::drop`.
66+
- Finally, uncomment the `panic!` inside `Foo::drop` and run it. Ask the class:
67+
which destructors run before the abort?
7168

7269
</details>

src/idiomatic/leveraging-the-type-system/raii/mutex.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,20 @@ fn main() {
2121
<details>
2222

2323
- A `Mutex` controls exclusive access to a value. Unlike earlier RAII examples,
24-
the resource here is not external but logical: the right to mutate shared
25-
data.
24+
the resource here is logical: temporary exclusive access to the data inside.
2625

27-
- This right is represented by a `MutexGuard`. Only one can exist at a time.
28-
While it lives, it provides `&mut T` access.
26+
- This right is represented by a `MutexGuard`. Only one guard for this mutex can
27+
exist at a time. While it lives, it provides `&mut T` access.
2928

3029
- Although `lock()` takes `&self`, it returns a `MutexGuard` with mutable
31-
access. This is possible through interior mutability: a common pattern for
32-
safe shared-state mutation.
30+
access. This works through _interior mutability_, where a type manages its own
31+
borrowing rules internally to allow mutation through `&self`.
3332

3433
- `MutexGuard` implements `Deref` and `DerefMut`, making access ergonomic. You
3534
lock the mutex, use the guard like a `&mut T`, and the lock is released
3635
automatically when the guard goes out of scope.
3736

38-
- The release is handled by `Drop`. There is no need to call a separate unlock
39-
function — this is RAII in action.
37+
- The release is handled by `Drop`. You never call an explicit unlock function.
38+
The guard’s `Drop` implementation releases the lock automatically.
4039

4140
</details>

src/idiomatic/leveraging-the-type-system/raii/scope_guard.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Scope Guards
22

3-
A scope guard uses the `Drop` trait to ensure cleanup code runs automatically
4-
when a scope exits — even if due to an error.
3+
A scope guard uses the `Drop` trait to run cleanup code automatically when a
4+
scope exits — even during unwinding.
55

6-
```rust,editable
6+
```rust,editable,compile_fail
77
use scopeguard::{ScopeGuard, guard};
88
use std::fs::{self, File};
99
use std::io::Write;
@@ -36,17 +36,18 @@ fn main() {
3636

3737
<details>
3838

39-
- This example simulates an HTTP download. We create a temporary file first,
39+
- This example models a download workflow. We create a temporary file first,
4040
then use a scope guard to ensure that the file is deleted if the download
4141
fails.
4242

4343
- The guard is placed directly after creating the file, so even if `writeln!()`
4444
fails, the file will still be cleaned up. This ordering is essential for
4545
correctness.
4646

47-
- The guard's closure runs on scope exit unless defused with
48-
`ScopeGuard::into_inner`. In the success path, we defuse it to preserve the
49-
file.
47+
- The guard's closure runs on scope exit unless it is _defused_ with
48+
`ScopeGuard::into_inner` (removing the value so the guard does nothing on
49+
drop). In the success path, we call `into_inner` so the guard will not delete
50+
the file.
5051

5152
- This pattern is useful when you want fallbacks or cleanup code to run
5253
automatically but only if success is not explicitly signaled.

0 commit comments

Comments
 (0)