Skip to content

Commit 39ae8f6

Browse files
committed
initial version of the raii chapters for idiomatic rust
1 parent 059b44b commit 39ae8f6

File tree

5 files changed

+424
-1
lines changed

5 files changed

+424
-1
lines changed

src/SUMMARY.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,10 @@
437437
- [Semantic Confusion](idiomatic/leveraging-the-type-system/newtype-pattern/semantic-confusion.md)
438438
- [Parse, Don't Validate](idiomatic/leveraging-the-type-system/newtype-pattern/parse-don-t-validate.md)
439439
- [Is It Encapsulated?](idiomatic/leveraging-the-type-system/newtype-pattern/is-it-encapsulated.md)
440-
440+
- [RAII](idiomatic/leveraging-the-type-system/raii.md)
441+
- [Drop Limitations](idiomatic/leveraging-the-type-system/raii/drop_limitations.md)
442+
- [Drop Bomb](idiomatic/leveraging-the-type-system/raii/drop_bomb.md)
443+
- [Scope Guards](idiomatic/leveraging-the-type-system/raii/scope_guards.md)
441444
---
442445

443446
# Final Words
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
minutes: 30
3+
---
4+
5+
# RAII and `Drop` in Practice
6+
7+
RAII (*Resource Acquisition Is Initialization*)
8+
means tying the lifetime of a resource to the lifetime of a value.
9+
10+
Rust applies RAII automatically for memory management.
11+
The `Drop` trait lets you extend this pattern to anything else.
12+
13+
```rust
14+
use std::sync::Mutex;
15+
16+
fn main() {
17+
let mux = Mutex::new(vec![1, 2, 3]);
18+
19+
{
20+
let mut data = mux.lock().unwrap();
21+
data.push(4); // lock held here
22+
} // lock automatically released here
23+
}
24+
```
25+
26+
<details>
27+
28+
- In the above example
29+
[the `Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html)
30+
owns its data: you can’t access the value inside without first acquiring the lock.
31+
32+
`mux.lock()` returns a
33+
[`MutexGuard`](https://doc.rust-lang.org/std/sync/struct.MutexGuard.html),
34+
which [dereferences](https://doc.rust-lang.org/std/ops/trait.DerefMut.html)
35+
to the data and implements [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html).
36+
37+
- You may recall from [the Memory Management chapter](../../memory-management/drop.md)
38+
that the [`Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html)
39+
lets you define what should happen when a resource is dropped.
40+
41+
- In [the Blocks and Scopes chapter](../../control-flow-basics/blocks-and-scopes.md),
42+
we saw the most common situation where a resource is dropped:
43+
when the scope of its _owner_ ends at the boundary of a block (`{}`).
44+
45+
- The use of
46+
[`std::mem::drop(val)`](https://doc.rust-lang.org/std/mem/fn.drop.html)
47+
allows you to _move_ a value out of scope before the block ends.
48+
49+
- There are also other scenarios where this can happen,
50+
such as when the value owning the resource is "shadowed" by another value:
51+
52+
```rust
53+
let a = String::from("foo");
54+
let a = 3; // ^ The previous string is dropped here
55+
// because we shadow its binding with a new value.
56+
```
57+
58+
- Recall also from [the Drop chapter](../../memory-management/drop.md)
59+
that for a composite type such as a `struct`, all its fields will be dropped
60+
when the struct itself is dropped.
61+
If a field implements the `Drop` trait, its `Drop::drop`
62+
_trait_ method will also be invoked.
63+
64+
- In any scenario where the stack unwinds the value, it is guaranteed
65+
that the [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop)
66+
method of a value `a` will be called.
67+
68+
- This holds true for happy paths such as:
69+
70+
- Exiting a block or function scope.
71+
72+
- Returning early with an explicit `return` statement,
73+
or implicitly by using
74+
[the Try operator (`?`)](../../error-handling/try.md)
75+
to early-return `Option` or `Result` values.
76+
77+
- It also holds for unexpected scenarios where a `panic` is triggered, if:
78+
79+
- The stack unwinds on panic (which is the default),
80+
allowing for graceful cleanup of resources.
81+
82+
This unwind behavior can be overridden to instead
83+
[abort on panic](https://github.com/rust-lang/rust/blob/master/library/panic_abort/src/lib.rs).
84+
85+
- No panic occurs within any of the `drop` methods
86+
invoked before reaching the `drop` call of the object `a`.
87+
88+
- Note that
89+
[an explicit exit of the program](https://doc.rust-lang.org/std/process/fn.exit.html),
90+
as sometimes used in CLI tools, terminates the process immediately.
91+
In other words, the stack is not unwound in this case,
92+
and the `drop` method will not be called.
93+
94+
- `Drop` is a great fit for use cases like `Mutex`.
95+
96+
When the guard goes out of scope, [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop)
97+
is called and unlocks the mutex automatically.
98+
99+
In contrast to C++ or Java, where you often have to unlock manually
100+
or use a `lock/unlock` pattern, Rust ensures the
101+
lock *cannot* be forgotten, thanks to the compiler.
102+
103+
- In other scenarios, the `Drop` trait shows its limitations.
104+
Next, we'll look at what those are and how we can
105+
address them.
106+
107+
## More to explore
108+
109+
To learn more about building synchronization primitives,
110+
consider reading [*Rust Atomics and Locks* by Mara Bos](https://marabos.nl/atomics/).
111+
112+
The book demonstrates, among other topics, how `Drop`
113+
and RAII work together in constructs like `Mutex`.
114+
115+
116+
</details>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Drop Bombs: Enforcing API Correctness
2+
3+
Use `Drop` to enforce invariants and detect incorrect API usage.
4+
A "drop bomb" panics if not defused.
5+
6+
```rust
7+
struct Transaction {
8+
active: bool,
9+
}
10+
11+
impl Transaction {
12+
fn start() -> Self {
13+
Self { active: true }
14+
}
15+
16+
fn commit(mut self) {
17+
self.active = false;
18+
// Dropped after this point, no panic
19+
}
20+
21+
fn rollback(mut self) {
22+
self.active = false;
23+
// Dropped after this point, no panic
24+
}
25+
}
26+
27+
impl Drop for Transaction {
28+
fn drop(&mut self) {
29+
if self.active {
30+
panic!("Transaction dropped without commit or roll back!");
31+
}
32+
}
33+
}
34+
```
35+
36+
<details>
37+
38+
- The example above uses the drop bomb pattern to enforce at runtime that a transaction
39+
is never dropped in an unfinished state. This applies to cases such as a database
40+
transaction that remains active in an external system.
41+
42+
In this example, the programmer must finalize the transaction explicitly,
43+
either by committing it or rolling it back to undo any changes.
44+
45+
- In the context of FFI, where cross-boundary references are involved, it is often necessary
46+
to ensure that manually allocated memory from the guest language is cleaned up through
47+
an explicit call to a safe API function.
48+
49+
- Similar to unsafe code, it is recommended that APIs with expectations like these
50+
are clearly documented under a Panic section. This helps ensure that users of the API
51+
are aware of the consequences of misuse.
52+
53+
Ideally, drop bombs should be used only in internal APIs to catch bugs early,
54+
without placing implicit runtime obligations on library users.
55+
56+
- If there is a way to restore the system to a valid state using a fallback
57+
in the Drop implementation, it is advisable to restrict the use of drop bombs
58+
to Debug mode. In Release mode, the Drop implementation could fall back to
59+
safe cleanup logic while still logging the incident as an error.
60+
61+
- Advanced use cases might also rely on the following patterns:
62+
63+
- [`Option<T>` with `.take()`](https://doc.rust-lang.org/std/option/enum.Option.html#method.take):
64+
This allows you to move out the resource in a controlled way, preventing
65+
accidental double cleanup or use-after-drop errors.
66+
67+
- [`ManuallyDrop`](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html):
68+
A zero-cost wrapper that disables the automatic drop behavior of a value,
69+
making manual cleanup required and explicit.
70+
71+
- The [`drop_bomb` crate](https://docs.rs/drop_bomb/latest/drop_bomb/)
72+
provides a way to enforce that certain values are not dropped unless explicitly defused.
73+
It can be added to an existing struct and exposes a `.defuse()` method to make dropping safe.
74+
The crate also includes a `DebugDropBomb` variant for use in debug-only builds.
75+
76+
## More to explore
77+
78+
Rust does not currently support full linear types or typestate programming
79+
in the core language. This means the compiler cannot guarantee that a resource
80+
was used exactly once or finalized before being dropped.
81+
82+
Drop bombs serve as a runtime mechanism to enforce such usage invariants manually.
83+
This is typically done in a Drop implementation that panics if a required method,
84+
such as `.commit()`, was not called before the value went out of scope.
85+
86+
There is an open RFC issue and discussion about linear types in Rust:
87+
<https://github.com/rust-lang/rfcs/issues/814>.
88+
89+
</details>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# The limitations of `Drop`
2+
3+
While `Drop` works well for cases
4+
like synchronization primitives, its use becomes more
5+
questionable when dealing with I/O or unsafe resources.
6+
7+
```rust
8+
use std::fs::File;
9+
use std::io::{self, Write};
10+
11+
fn write_log() -> io::Result<()> {
12+
let mut file = File::create("log.txt")?;
13+
// ^ ownership of the (OS) file handle starts here
14+
15+
writeln!(file, "Logging a message...")?;
16+
Ok(())
17+
} // file handle goes out of scope here
18+
```
19+
20+
<details>
21+
22+
- In the earlier example, our `File` resource owns a file handle
23+
provided by the operating system.
24+
25+
[As stated in the documentation](https://doc.rust-lang.org/std/fs/struct.File.html):
26+
27+
> Files are automatically closed when they go out of scope.
28+
> Errors detected on closing are ignored by the implementation of Drop.
29+
30+
- This highlights a key limitation of the `Drop` trait:
31+
it cannot propagate errors to the caller. In other words,
32+
fallible cleanup logic cannot be handled by the code using the `File`.
33+
34+
This becomes clear when looking at the
35+
[definition of the `Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html):
36+
37+
```rust
38+
trait Drop {
39+
fn drop(&mut self);
40+
}
41+
```
42+
43+
Since `drop` does not return a `Result`, any error that occurs during cleanup
44+
cannot be surfaced or recovered from. This is by design:
45+
`drop` is invoked automatically when a value is popped off the stack during
46+
unwinding, leaving no opportunity for error handling.
47+
48+
- One workaround is to panic inside `drop` when a failure occurs.
49+
However, this is risky—if a panic happens while the stack is already unwinding,
50+
the program will abort immediately, and remaining resources will not be cleaned up.
51+
52+
While panicking in `drop` can serve certain purposes (see
53+
[the next chapter on "drop bombs"](./drop_bomb.md)), it should be used sparingly
54+
and with full awareness of the consequences.
55+
56+
- Another drawback of `drop` is that its execution is implicit and non-deterministic
57+
in terms of timing. You cannot control *when* a value is dropped. And in fact as
58+
discussed in previous slide it might never even run at all, leaving the external
59+
resource in an undefined state.
60+
61+
This matters particularly for I/O: normally you might set a timeout on blocking
62+
operations, but when I/O occurs in a `drop` implementation, you have no way to
63+
enforce such constraints.
64+
65+
Returning to the `File` example: if the file handle hangs during close (e.g.,
66+
due to OS-level buffering or locking), the drop operation could block indefinitely.
67+
Since the call to `drop` happens implicitly and outside your control,
68+
there's no way to apply a timeout or fallback mechanism.
69+
70+
- For smart pointers and synchronization primitives, none of these drawbacks matter,
71+
since the operations are nearly instant and a program panic does not cause undefined behavior.
72+
The poisoned state disappears along with the termination of the program.
73+
74+
- For use cases such as I/O or FFI, it may be preferable to let the user
75+
clean up resources explicitly using a close function.
76+
77+
However, this approach cannot be enforced at the type level.
78+
If explicit cleanup is part of your API contract, you might choose to
79+
panic in drop when the resource has not been properly closed.
80+
This can help catch contract violations at runtime.
81+
82+
This is one situation where drop bombs are useful,
83+
which we will discuss next.
84+
85+
</details>

0 commit comments

Comments
 (0)