Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
39ae8f6
initial version of the raii chapters for idiomatic rust
GlenDC Jul 15, 2025
f4269b7
apply dprint fmt
GlenDC Jul 15, 2025
bb88a79
fix build (en) step: scopeguard crate import
GlenDC Jul 15, 2025
5a4838e
integrate feedback of first reviews (RAII)
GlenDC Jul 26, 2025
d804144
add new `RAII` intro segment
GlenDC Jul 26, 2025
5c2ec8c
further improvements to RAII intro chapter
GlenDC Jul 27, 2025
0baa990
fix typo
GlenDC Jul 27, 2025
9fa819a
improve RAII intro segment further based on Luca's feedbacl
GlenDC Aug 1, 2025
4ebb43f
apply feedback RAII and rewrite; draft 1
GlenDC Aug 2, 2025
42a4237
improve drop bomb code example
GlenDC Aug 2, 2025
2923cf3
prepare raii chapter for next reviews
GlenDC Aug 3, 2025
48c5baa
fix raii drop bomb example
GlenDC Aug 3, 2025
cc5f3b5
address feedback 1/2 of @randomPoison
GlenDC Aug 30, 2025
55e4753
address @randomPoison feedback 2/2
GlenDC Aug 30, 2025
1d67c9b
Merge branch 'main' into raii
GlenDC Sep 29, 2025
4c408b7
address more feedback
GlenDC Nov 3, 2025
8f017de
Merge branch 'main' into raii
GlenDC Nov 3, 2025
26c92f4
address more feedback
GlenDC Nov 30, 2025
1b1a3ab
Merge branch 'main' into raii
GlenDC Nov 30, 2025
67cefcc
run dprint fmt
GlenDC Nov 30, 2025
3789e70
apply rem. feedback to raii slides + introduce drop_skipped slide
GlenDC Nov 30, 2025
4a79ba4
add 2 extra slides
GlenDC Nov 30, 2025
cacc981
improve the raii chapter slides based on the new STYLE guidelines
GlenDC Nov 30, 2025
499d0c5
apply feedback from Kevin B.
GlenDC Dec 1, 2025
bc2cf46
apply rem. feedback from previously unresolved thread
GlenDC Dec 1, 2025
169f357
address 1/2 of feedback from yesterday
GlenDC Dec 2, 2025
809fc4e
address more feedback regarding existing slides + 1 new slide
GlenDC Dec 2, 2025
897f7b2
improve drop skipped example
GlenDC Dec 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,15 @@
- [Semantic Confusion](idiomatic/leveraging-the-type-system/newtype-pattern/semantic-confusion.md)
- [Parse, Don't Validate](idiomatic/leveraging-the-type-system/newtype-pattern/parse-don-t-validate.md)
- [Is It Encapsulated?](idiomatic/leveraging-the-type-system/newtype-pattern/is-it-encapsulated.md)
- [RAII](idiomatic/leveraging-the-type-system/raii.md)
- [Drop Skipped](idiomatic/leveraging-the-type-system/raii/drop_skipped.md)
- [Mutex](idiomatic/leveraging-the-type-system/raii/mutex.md)
- [Drop Guards](idiomatic/leveraging-the-type-system/raii/drop_guards.md)
- [Drop Bomb](idiomatic/leveraging-the-type-system/raii/drop_bomb.md)
- [Drop Bomb Forget](idiomatic/leveraging-the-type-system/raii/drop_bomb_forget.md)
- [forget and drop functions](idiomatic/leveraging-the-type-system/raii/forget_and_drop.md)
- [Scope Guard](idiomatic/leveraging-the-type-system/raii/scope_guard.md)
- [Drop Option](idiomatic/leveraging-the-type-system/raii/drop_option.md)
- [Extension Traits](idiomatic/leveraging-the-type-system/extension-traits.md)
- [Extending Foreign Types](idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md)
- [Method Resolution Conflicts](idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md)
Expand Down
93 changes: 93 additions & 0 deletions src/idiomatic/leveraging-the-type-system/raii.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
minutes: 60
---

# RAII: `Drop` trait

RAII (**R**esource **A**cquisition **I**s **I**nitialization) ties the lifetime
of a resource to the lifetime of a value.

[Rust uses RAII to manage memory](https://doc.rust-lang.org/rust-by-example/scope/raii.html),
and the `Drop` trait allows you to extend this to other resources, such as file
descriptors or locks.

```rust,editable
pub struct File(std::os::fd::RawFd);

impl File {
pub fn open(path: &str) -> Result<Self, std::io::Error> {
// [...]
Ok(Self(0))
}

pub fn read_to_end(&mut self) -> Result<Vec<u8>, std::io::Error> {
// [...]
Ok(b"example".to_vec())
}

pub fn close(self) -> Result<(), std::io::Error> {
// [...]
Ok(())
}
}

fn main() -> Result<(), std::io::Error> {
let mut file = File::open("example.txt")?;
println!("content: {:?}", file.read_to_end()?);
Ok(())
}
```

<details>

- Easy to miss: `file.close()` is never called. Ask the class if they noticed.

- To release the file descriptor correctly, `file.close()` must be called after
the last use — and also in early-return paths in case of errors.

- Instead of relying on the user to call `close()`, we can implement the `Drop`
trait to release the resource automatically. This ties cleanup to the lifetime
of the `File` value.

```rust,compile_fail
impl Drop for File {
fn drop(&mut self) {
// libc::close(...);
println!("file descriptor was closed");
}
}
```

- Note that `Drop::drop()` cannot return a `Result`. Any failures must be
handled internally or ignored. In the standard library, errors during FD
closure inside `Drop` are silently discarded. See the implementation:
<https://doc.rust-lang.org/src/std/os/fd/owned.rs.html#169-196>

- When is `Drop::drop` called?

Normally, when the `file` variable in `main()` goes out of scope (either on
return or due to a panic), `drop()` is called automatically.

If the file is moved into another function, the value is dropped when that
function returns — not in `main`.

In contrast, C++ runs destructors in the original scope even for moved-from
values.

- Demo: insert `panic!("oops")` at the start of `read_to_end()` and run it.
`drop()` still runs during unwinding.

### More to Explore

The `Drop` trait has another important limitation: it is not `async`.

This means you cannot `await` inside a destructor, which is often needed when
cleaning up asynchronous resources like sockets, database connections, or tasks
that must signal completion to another system.

- Learn more:
<https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_drop.html>
- There is an experimental `AsyncDrop` trait available on nightly:
<https://doc.rust-lang.org/nightly/std/future/trait.AsyncDrop.html>

</details>
88 changes: 88 additions & 0 deletions src/idiomatic/leveraging-the-type-system/raii/drop_bomb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Drop Bombs: Enforcing API Correctness

Use `Drop` to enforce invariants and detect incorrect API usage. A "drop bomb"
panics if a value is dropped without being explicitly finalized.

This pattern is often used when the finalizing operation (like `commit()` or
`rollback()`) needs to return a `Result`, which cannot be done from `Drop`.

```rust,editable
use std::io::{self, Write};

struct Transaction {
active: bool,
}

impl Transaction {
fn start() -> Self {
Self { active: true }
}

fn commit(mut self) -> io::Result<()> {
writeln!(io::stdout(), "COMMIT")?;
self.active = false;
Ok(())
}
}

impl Drop for Transaction {
fn drop(&mut self) {
if self.active {
panic!("Transaction dropped without commit!");
}
}
}

fn main() -> io::Result<()> {
let tx = Transaction::start();
// Use `tx` to build the transaction, then commit it.
// Comment out the call to `commit` to see the panic.
tx.commit()?;
Ok(())
}
```

<details>

- In some systems, a value must be finalized by a specific API before it is
dropped.

For example, a `Transaction` might need to be committed or rolled back.

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

- The finalizing operation (such as `commit()`) usually takes `self` by value.
This ensures that once the transaction is finalized, the original object can
no longer be used.

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

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

- If cleanup can safely happen in `Drop`, some APIs choose to panic only in
debug builds. Whether this is appropriate depends on the guarantees your API
must enforce.

- Panicking in release builds is reasonable when silent misuse would cause major
correctness or security problems.

- Question: Why do we need an `active` flag inside `Transaction`? Why can't
`drop()` panic unconditionally?

Expected answer: `commit()` takes `self` by value and runs `drop()`, which
would panic.

## More to explore

Several related patterns help enforce correct teardown or prevent accidental
drops.

- The [`drop_bomb` crate](https://docs.rs/drop_bomb/latest/drop_bomb/): A small
utility that panics if dropped unless explicitly defused with `.defuse()`.
Comes with a `DebugDropBomb` variant that only activates in debug builds.

</details>
56 changes: 56 additions & 0 deletions src/idiomatic/leveraging-the-type-system/raii/drop_bomb_forget.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Drop Bombs: using `std::mem::forget`

```rust,editable
use std::io::{self, Write};

struct Transaction;

impl Transaction {
fn start() -> Self {
Transaction
}

fn commit(self) -> io::Result<()> {
writeln!(io::stdout(), "COMMIT")?;

// Defuse the drop bomb by preventing Drop from ever running.
std::mem::forget(self);

Ok(())
}
}

impl Drop for Transaction {
fn drop(&mut self) {
// This is the "drop bomb"
panic!("Transaction dropped without commit!");
}
}

fn main() -> io::Result<()> {
let tx = Transaction::start();
// Use `tx` to build the transaction, then commit it.
// Comment out the call to `commit` to see the panic.
tx.commit()?;
Ok(())
}
```

<details>

In the previous slide we saw that calling
[`std::mem::forget`](https://doc.rust-lang.org/std/mem/fn.forget.html) prevents
`Drop::drop` from ever running.

Remember that `mem::forget()` takes ownership of a value and prevents its
**destructor** (`Drop::drop()`) from running. If the forgotten value owned heap
allocated memory that would normally be freed in its `drop()` implementation,
this will result in a memory leak. That is not the case for the `Transaction` in
the example above, since it does not own any heap memory.

However, this avoids needing a runtime flag: when the transaction is
successfully committed, we can _defuse_ the drop bomb — meaning we prevent
`Drop` from running — by calling `std::mem::forget` on the value instead of
letting its destructor run.

</details>
65 changes: 65 additions & 0 deletions src/idiomatic/leveraging-the-type-system/raii/drop_guards.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Drop Guards

A **drop guard** in Rust is a temporary object that performs some kind of
cleanup when it goes out of scope. In the case of `Mutex`, the `lock` method
returns a `MutexGuard` that automatically unlocks the mutex on `drop`:

```rust
struct Mutex {
is_locked: bool,
}

struct MutexGuard<'a> {
mutex: &'a mut Mutex,
}

impl Mutex {
fn new() -> Self {
Self { is_locked: false }
}

fn lock(&mut self) -> MutexGuard<'_> {
self.is_locked = true;
MutexGuard { mutex: self }
}
}

impl Drop for MutexGuard<'_> {
fn drop(&mut self) {
self.mutex.is_locked = false;
}
}
```

<details>

- The example above shows a simplified `Mutex` and its associated guard.

- Even though it is not a production-ready implementation, it illustrates the
core idea:

- the guard represents exclusive access,
- and its `Drop` implementation releases the lock when it goes out of scope.

## More to Explore

This example shows a C++ style mutex that does not contain the data it protects.
While this is non-idiomatic in Rust, the goal here is only to illustrate the
core idea of a drop guard, not to demonstrate a proper Rust mutex design.

For brevity, several features are omitted:

- A real `Mutex<T>` stores the protected value inside the mutex.\
This toy example omits the value entirely to focus only on the drop guard
mechanism.
- Ergonomic access via `Deref` and `DerefMut` on `MutexGuard` (letting the guard
behave like a `&T` or `&mut T`).
- A fully blocking `.lock()` method and a non-blocking `try_lock` variant.

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

</details>
Loading