Skip to content

Commit 4a79ba4

Browse files
committed
add 2 extra slides
this completes the raii chapter
1 parent 3789e70 commit 4a79ba4

File tree

6 files changed

+140
-12
lines changed

6 files changed

+140
-12
lines changed

src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,9 @@
446446
- [Drop Guards](idiomatic/leveraging-the-type-system/raii/drop_guards.md)
447447
- [Drop Bomb](idiomatic/leveraging-the-type-system/raii/drop_bomb.md)
448448
- [Drop Skipped](idiomatic/leveraging-the-type-system/raii/drop_skipped.md)
449+
- [Drop Bomb Forget](idiomatic/leveraging-the-type-system/raii/drop_bomb_forget.md)
449450
- [Scope Guard](idiomatic/leveraging-the-type-system/raii/scope_guard.md)
451+
- [Drop Option](idiomatic/leveraging-the-type-system/raii/drop_option.md)
450452
- [Extension Traits](idiomatic/leveraging-the-type-system/extension-traits.md)
451453
- [Extending Foreign Types](idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md)
452454
- [Method Resolution Conflicts](idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md)

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,6 @@ struct Transaction {
1414
}
1515
1616
impl Transaction {
17-
/// Begin a [`Transaction`].
18-
///
19-
/// ## Panics
20-
///
21-
/// Panics if the transaction is dropped without
22-
/// calling [`Self::commit`] or [`Self::rollback`].
2317
fn start() -> Self {
2418
Self { active: true }
2519
}
@@ -34,7 +28,7 @@ impl Transaction {
3428
impl Drop for Transaction {
3529
fn drop(&mut self) {
3630
if self.active {
37-
panic!("Transaction dropped without commit or rollback!");
31+
panic!("Transaction dropped without commit!");
3832
}
3933
}
4034
}
@@ -44,6 +38,7 @@ fn main() -> io::Result<()> {
4438
// Use `tx` to build the transaction, then commit it.
4539
// Comment out the call to `commit` to see the panic.
4640
tx.commit()?;
41+
Ok(())
4742
}
4843
```
4944

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Drop Bombs: using `std::mem::forget`
2+
3+
```rust,editable
4+
use std::io::{self, Write};
5+
6+
struct Transaction;
7+
8+
impl Transaction {
9+
fn start() -> Self {
10+
Transaction
11+
}
12+
13+
fn commit(self) -> io::Result<()> {
14+
writeln!(io::stdout(), "COMMIT")?;
15+
16+
// Defuse the drop bomb by preventing Drop from ever running.
17+
std::mem::forget(self);
18+
19+
Ok(())
20+
}
21+
}
22+
23+
impl Drop for Transaction {
24+
fn drop(&mut self) {
25+
// This is the "drop bomb"
26+
panic!("Transaction dropped without commit!");
27+
}
28+
}
29+
30+
fn main() -> io::Result<()> {
31+
let tx = Transaction::start();
32+
// Use `tx` to build the transaction, then commit it.
33+
// Comment out the call to `commit` to see the panic.
34+
tx.commit()?;
35+
Ok(())
36+
}
37+
```
38+
39+
<details>
40+
41+
In the previous slide we saw that calling
42+
[`std::mem::forget`](https://doc.rust-lang.org/std/mem/fn.forget.html) prevents
43+
`Drop::drop` from ever running.
44+
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.
48+
49+
</details>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Drop: Option
2+
3+
```rust,editable
4+
struct File(Option<Handle>);
5+
6+
impl File {
7+
fn open(path: &'static str) -> std::io::Result<Self> {
8+
Ok(Self(Some(Handle { path })))
9+
}
10+
11+
fn write(&mut self, data: &str) -> std::io::Result<()> {
12+
match &mut self.0 {
13+
Some(handle) => println!("write '{data}' to file '{}'", handle.path),
14+
None => unreachable!(),
15+
}
16+
Ok(())
17+
}
18+
19+
fn close(mut self) -> std::io::Result<&'static str> {
20+
Ok(self.0.take().unwrap().path)
21+
}
22+
}
23+
24+
impl Drop for File {
25+
fn drop(&mut self) {
26+
if let Some(handle) = self.0.take() {
27+
println!("automatically close handle for file: {}", handle.path);
28+
}
29+
}
30+
}
31+
32+
struct Handle {
33+
path: &'static str,
34+
}
35+
impl Drop for Handle {
36+
fn drop(&mut self) {
37+
println!("close handle for file: {}", self.path)
38+
}
39+
}
40+
41+
fn main() -> std::io::Result<()> {
42+
let mut file = File::open("foo.txt")?;
43+
file.write("hello")?;
44+
println!("manually closed file: {}", file.close()?);
45+
Ok(())
46+
}
47+
```
48+
49+
<details>
50+
51+
- In this example we want to let the user call `close()` manually so that errors
52+
from closing the file can be reported explicitly.
53+
54+
- At the same time we still want RAII semantics: if the user forgets to call
55+
`close()`, the handle must be cleaned up automatically in `Drop`.
56+
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()`.
62+
63+
- 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.
67+
68+
## More to explore
69+
70+
Instead of `Option` we could use
71+
[`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.
75+
76+
The [_scopeguard_ example](./scope_guard.md) on the previous slide shows how
77+
`ManuallyDrop` can replace `Option` to avoid handling `None` in places where the
78+
value should always exist.
79+
80+
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.
83+
84+
</details>

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,4 @@ fn main() {
6969
- Finally, consider what happens when you also uncomment the `panic!` inside
7070
`Foo::drop`.
7171

72-
TODO...
73-
7472
</details>

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
A scope guard uses the `Drop` trait to ensure cleanup code runs automatically
44
when a scope exits — even if due to an error.
55

6-
```rust,editable,compile_fail
6+
```rust,editable
77
use scopeguard::{ScopeGuard, guard};
88
use std::fs::{self, File};
99
use std::io::Write;
@@ -27,8 +27,8 @@ fn main() {
2727
2828
if download_successful() {
2929
// Download succeeded, keep the file
30-
let _path = ScopeGuard::into_inner(cleanup);
31-
println!("Download complete!");
30+
let path = ScopeGuard::into_inner(cleanup);
31+
println!("Download '{path}' complete!");
3232
}
3333
// Otherwise, the guard runs and deletes the file
3434
}

0 commit comments

Comments
 (0)