1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-08-09 16:58:19 +02:00

apply dprint fmt

This commit is contained in:
Glen De Cauwsemaecker
2025-07-15 20:54:50 +02:00
parent 39ae8f65ce
commit f4269b7f75
5 changed files with 147 additions and 141 deletions

View File

@ -441,6 +441,7 @@
- [Drop Limitations](idiomatic/leveraging-the-type-system/raii/drop_limitations.md) - [Drop Limitations](idiomatic/leveraging-the-type-system/raii/drop_limitations.md)
- [Drop Bomb](idiomatic/leveraging-the-type-system/raii/drop_bomb.md) - [Drop Bomb](idiomatic/leveraging-the-type-system/raii/drop_bomb.md)
- [Scope Guards](idiomatic/leveraging-the-type-system/raii/scope_guards.md) - [Scope Guards](idiomatic/leveraging-the-type-system/raii/scope_guards.md)
--- ---
# Final Words # Final Words

View File

@ -4,11 +4,11 @@ minutes: 30
# RAII and `Drop` in Practice # RAII and `Drop` in Practice
RAII (*Resource Acquisition Is Initialization*) RAII (_Resource Acquisition Is Initialization_) means tying the lifetime of a
means tying the lifetime of a resource to the lifetime of a value. resource to the lifetime of a value.
Rust applies RAII automatically for memory management. Rust applies RAII automatically for memory management. The `Drop` trait lets you
The `Drop` trait lets you extend this pattern to anything else. extend this pattern to anything else.
```rust ```rust
use std::sync::Mutex; use std::sync::Mutex;
@ -26,28 +26,31 @@ fn main() {
<details> <details>
- In the above example - In the above example
[the `Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) [the `Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) owns its
owns its data: you can’t access the value inside without first acquiring the lock. data: you can’t access the value inside without first acquiring the lock.
`mux.lock()` returns a `mux.lock()` returns a
[`MutexGuard`](https://doc.rust-lang.org/std/sync/struct.MutexGuard.html), [`MutexGuard`](https://doc.rust-lang.org/std/sync/struct.MutexGuard.html),
which [dereferences](https://doc.rust-lang.org/std/ops/trait.DerefMut.html) which [dereferences](https://doc.rust-lang.org/std/ops/trait.DerefMut.html) to
to the data and implements [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html). the data and implements
[`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html).
- You may recall from [the Memory Management chapter](../../memory-management/drop.md) - You may recall from
that the [`Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html) [the Memory Management chapter](../../memory-management/drop.md) that the
lets you define what should happen when a resource is dropped. [`Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html) lets you
define what should happen when a resource is dropped.
- In [the Blocks and Scopes chapter](../../control-flow-basics/blocks-and-scopes.md), - In
we saw the most common situation where a resource is dropped: [the Blocks and Scopes chapter](../../control-flow-basics/blocks-and-scopes.md),
when the scope of its _owner_ ends at the boundary of a block (`{}`). we saw the most common situation where a resource is dropped: when the scope
of its _owner_ ends at the boundary of a block (`{}`).
- The use of - The use of
[`std::mem::drop(val)`](https://doc.rust-lang.org/std/mem/fn.drop.html) [`std::mem::drop(val)`](https://doc.rust-lang.org/std/mem/fn.drop.html)
allows you to _move_ a value out of scope before the block ends. allows you to _move_ a value out of scope before the block ends.
- There are also other scenarios where this can happen, - There are also other scenarios where this can happen, such as when the value
such as when the value owning the resource is "shadowed" by another value: owning the resource is "shadowed" by another value:
```rust ```rust
let a = String::from("foo"); let a = String::from("foo");
@ -55,62 +58,59 @@ fn main() {
// because we shadow its binding with a new value. // because we shadow its binding with a new value.
``` ```
- Recall also from [the Drop chapter](../../memory-management/drop.md) - Recall also from [the Drop chapter](../../memory-management/drop.md) that
that for a composite type such as a `struct`, all its fields will be dropped for a composite type such as a `struct`, all its fields will be dropped when
when the struct itself is dropped. the struct itself is dropped. If a field implements the `Drop` trait, its
If a field implements the `Drop` trait, its `Drop::drop` `Drop::drop` _trait_ method will also be invoked.
_trait_ method will also be invoked.
- In any scenario where the stack unwinds the value, it is guaranteed - In any scenario where the stack unwinds the value, it is guaranteed that the
that the [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop) [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop)
method of a value `a` will be called. method of a value `a` will be called.
- This holds true for happy paths such as: - This holds true for happy paths such as:
- Exiting a block or function scope. - Exiting a block or function scope.
- Returning early with an explicit `return` statement, - Returning early with an explicit `return` statement, or implicitly by
or implicitly by using using [the Try operator (`?`)](../../error-handling/try.md) to
[the Try operator (`?`)](../../error-handling/try.md) early-return `Option` or `Result` values.
to early-return `Option` or `Result` values.
- It also holds for unexpected scenarios where a `panic` is triggered, if: - It also holds for unexpected scenarios where a `panic` is triggered, if:
- The stack unwinds on panic (which is the default), - The stack unwinds on panic (which is the default), allowing for graceful
allowing for graceful cleanup of resources. cleanup of resources.
This unwind behavior can be overridden to instead This unwind behavior can be overridden to instead
[abort on panic](https://github.com/rust-lang/rust/blob/master/library/panic_abort/src/lib.rs). [abort on panic](https://github.com/rust-lang/rust/blob/master/library/panic_abort/src/lib.rs).
- No panic occurs within any of the `drop` methods - No panic occurs within any of the `drop` methods invoked before reaching
invoked before reaching the `drop` call of the object `a`. the `drop` call of the object `a`.
- Note that - Note that
[an explicit exit of the program](https://doc.rust-lang.org/std/process/fn.exit.html), [an explicit exit of the program](https://doc.rust-lang.org/std/process/fn.exit.html),
as sometimes used in CLI tools, terminates the process immediately. as sometimes used in CLI tools, terminates the process immediately. In other
In other words, the stack is not unwound in this case, words, the stack is not unwound in this case, and the `drop` method will not
and the `drop` method will not be called. be called.
- `Drop` is a great fit for use cases like `Mutex`. - `Drop` is a great fit for use cases like `Mutex`.
When the guard goes out of scope, [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop) When the guard goes out of scope,
[`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop)
is called and unlocks the mutex automatically. is called and unlocks the mutex automatically.
In contrast to C++ or Java, where you often have to unlock manually In contrast to C++ or Java, where you often have to unlock manually or use a
or use a `lock/unlock` pattern, Rust ensures the `lock/unlock` pattern, Rust ensures the lock _cannot_ be forgotten, thanks to
lock *cannot* be forgotten, thanks to the compiler. the compiler.
- In other scenarios, the `Drop` trait shows its limitations. - In other scenarios, the `Drop` trait shows its limitations. Next, we'll look
Next, we'll look at what those are and how we can at what those are and how we can address them.
address them.
## More to explore ## More to explore
To learn more about building synchronization primitives, To learn more about building synchronization primitives, consider reading
consider reading [*Rust Atomics and Locks* by Mara Bos](https://marabos.nl/atomics/). [_Rust Atomics and Locks_ by Mara Bos](https://marabos.nl/atomics/).
The book demonstrates, among other topics, how `Drop`
and RAII work together in constructs like `Mutex`.
The book demonstrates, among other topics, how `Drop` and RAII work together in
constructs like `Mutex`.
</details> </details>

View File

@ -1,7 +1,7 @@
# Drop Bombs: Enforcing API Correctness # Drop Bombs: Enforcing API Correctness
Use `Drop` to enforce invariants and detect incorrect API usage. Use `Drop` to enforce invariants and detect incorrect API usage. A "drop bomb"
A "drop bomb" panics if not defused. panics if not defused.
```rust ```rust
struct Transaction { struct Transaction {
@ -35,28 +35,28 @@ impl Drop for Transaction {
<details> <details>
- The example above uses the drop bomb pattern to enforce at runtime that a transaction - The example above uses the drop bomb pattern to enforce at runtime that a
is never dropped in an unfinished state. This applies to cases such as a database transaction is never dropped in an unfinished state. This applies to cases
transaction that remains active in an external system. such as a database transaction that remains active in an external system.
In this example, the programmer must finalize the transaction explicitly, In this example, the programmer must finalize the transaction explicitly,
either by committing it or rolling it back to undo any changes. either by committing it or rolling it back to undo any changes.
- In the context of FFI, where cross-boundary references are involved, it is often necessary - In the context of FFI, where cross-boundary references are involved, it is
to ensure that manually allocated memory from the guest language is cleaned up through often necessary to ensure that manually allocated memory from the guest
an explicit call to a safe API function. language is cleaned up through an explicit call to a safe API function.
- Similar to unsafe code, it is recommended that APIs with expectations like these - Similar to unsafe code, it is recommended that APIs with expectations like
are clearly documented under a Panic section. This helps ensure that users of the API these are clearly documented under a Panic section. This helps ensure that
are aware of the consequences of misuse. users of the API are aware of the consequences of misuse.
Ideally, drop bombs should be used only in internal APIs to catch bugs early, Ideally, drop bombs should be used only in internal APIs to catch bugs early,
without placing implicit runtime obligations on library users. without placing implicit runtime obligations on library users.
- If there is a way to restore the system to a valid state using a fallback - If there is a way to restore the system to a valid state using a fallback in
in the Drop implementation, it is advisable to restrict the use of drop bombs the Drop implementation, it is advisable to restrict the use of drop bombs to
to Debug mode. In Release mode, the Drop implementation could fall back to Debug mode. In Release mode, the Drop implementation could fall back to safe
safe cleanup logic while still logging the incident as an error. cleanup logic while still logging the incident as an error.
- Advanced use cases might also rely on the following patterns: - Advanced use cases might also rely on the following patterns:
@ -68,20 +68,22 @@ impl Drop for Transaction {
A zero-cost wrapper that disables the automatic drop behavior of a value, A zero-cost wrapper that disables the automatic drop behavior of a value,
making manual cleanup required and explicit. making manual cleanup required and explicit.
- The [`drop_bomb` crate](https://docs.rs/drop_bomb/latest/drop_bomb/) - The [`drop_bomb` crate](https://docs.rs/drop_bomb/latest/drop_bomb/) provides
provides a way to enforce that certain values are not dropped unless explicitly defused. a way to enforce that certain values are not dropped unless explicitly
It can be added to an existing struct and exposes a `.defuse()` method to make dropping safe. defused. It can be added to an existing struct and exposes a `.defuse()`
The crate also includes a `DebugDropBomb` variant for use in debug-only builds. method to make dropping safe. The crate also includes a `DebugDropBomb`
variant for use in debug-only builds.
## More to explore ## More to explore
Rust does not currently support full linear types or typestate programming Rust does not currently support full linear types or typestate programming in
in the core language. This means the compiler cannot guarantee that a resource the core language. This means the compiler cannot guarantee that a resource was
was used exactly once or finalized before being dropped. used exactly once or finalized before being dropped.
Drop bombs serve as a runtime mechanism to enforce such usage invariants manually. Drop bombs serve as a runtime mechanism to enforce such usage invariants
This is typically done in a Drop implementation that panics if a required method, manually. This is typically done in a Drop implementation that panics if a
such as `.commit()`, was not called before the value went out of scope. required method, such as `.commit()`, was not called before the value went out
of scope.
There is an open RFC issue and discussion about linear types in Rust: There is an open RFC issue and discussion about linear types in Rust:
<https://github.com/rust-lang/rfcs/issues/814>. <https://github.com/rust-lang/rfcs/issues/814>.

View File

@ -1,8 +1,7 @@
# The limitations of `Drop` # The limitations of `Drop`
While `Drop` works well for cases While `Drop` works well for cases like synchronization primitives, its use
like synchronization primitives, its use becomes more becomes more questionable when dealing with I/O or unsafe resources.
questionable when dealing with I/O or unsafe resources.
```rust ```rust
use std::fs::File; use std::fs::File;
@ -19,17 +18,17 @@ fn write_log() -> io::Result<()> {
<details> <details>
- In the earlier example, our `File` resource owns a file handle - In the earlier example, our `File` resource owns a file handle provided by the
provided by the operating system. operating system.
[As stated in the documentation](https://doc.rust-lang.org/std/fs/struct.File.html): [As stated in the documentation](https://doc.rust-lang.org/std/fs/struct.File.html):
> Files are automatically closed when they go out of scope. > Files are automatically closed when they go out of scope. Errors detected on
> Errors detected on closing are ignored by the implementation of Drop. > closing are ignored by the implementation of Drop.
- This highlights a key limitation of the `Drop` trait: - This highlights a key limitation of the `Drop` trait: it cannot propagate
it cannot propagate errors to the caller. In other words, errors to the caller. In other words, fallible cleanup logic cannot be handled
fallible cleanup logic cannot be handled by the code using the `File`. by the code using the `File`.
This becomes clear when looking at the This becomes clear when looking at the
[definition of the `Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html): [definition of the `Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html):
@ -41,45 +40,45 @@ fn write_log() -> io::Result<()> {
``` ```
Since `drop` does not return a `Result`, any error that occurs during cleanup Since `drop` does not return a `Result`, any error that occurs during cleanup
cannot be surfaced or recovered from. This is by design: cannot be surfaced or recovered from. This is by design: `drop` is invoked
`drop` is invoked automatically when a value is popped off the stack during automatically when a value is popped off the stack during unwinding, leaving
unwinding, leaving no opportunity for error handling. no opportunity for error handling.
- One workaround is to panic inside `drop` when a failure occurs. - One workaround is to panic inside `drop` when a failure occurs. However, this
However, this is risky—if a panic happens while the stack is already unwinding, is risky—if a panic happens while the stack is already unwinding, the program
the program will abort immediately, and remaining resources will not be cleaned up. will abort immediately, and remaining resources will not be cleaned up.
While panicking in `drop` can serve certain purposes (see While panicking in `drop` can serve certain purposes (see
[the next chapter on "drop bombs"](./drop_bomb.md)), it should be used sparingly [the next chapter on "drop bombs"](./drop_bomb.md)), it should be used
and with full awareness of the consequences. sparingly and with full awareness of the consequences.
- Another drawback of `drop` is that its execution is implicit and non-deterministic - Another drawback of `drop` is that its execution is implicit and
in terms of timing. You cannot control *when* a value is dropped. And in fact as non-deterministic in terms of timing. You cannot control _when_ a value is
discussed in previous slide it might never even run at all, leaving the external dropped. And in fact as discussed in previous slide it might never even run at
resource in an undefined state. all, leaving the external resource in an undefined state.
This matters particularly for I/O: normally you might set a timeout on blocking This matters particularly for I/O: normally you might set a timeout on
operations, but when I/O occurs in a `drop` implementation, you have no way to blocking operations, but when I/O occurs in a `drop` implementation, you have
enforce such constraints. no way to enforce such constraints.
Returning to the `File` example: if the file handle hangs during close (e.g., Returning to the `File` example: if the file handle hangs during close (e.g.,
due to OS-level buffering or locking), the drop operation could block indefinitely. due to OS-level buffering or locking), the drop operation could block
Since the call to `drop` happens implicitly and outside your control, indefinitely. Since the call to `drop` happens implicitly and outside your
there's no way to apply a timeout or fallback mechanism. control, there's no way to apply a timeout or fallback mechanism.
- For smart pointers and synchronization primitives, none of these drawbacks matter, - For smart pointers and synchronization primitives, none of these drawbacks
since the operations are nearly instant and a program panic does not cause undefined behavior. matter, since the operations are nearly instant and a program panic does not
The poisoned state disappears along with the termination of the program. cause undefined behavior. The poisoned state disappears along with the
termination of the program.
- For use cases such as I/O or FFI, it may be preferable to let the user - For use cases such as I/O or FFI, it may be preferable to let the user clean
clean up resources explicitly using a close function. up resources explicitly using a close function.
However, this approach cannot be enforced at the type level. However, this approach cannot be enforced at the type level. If explicit
If explicit cleanup is part of your API contract, you might choose to cleanup is part of your API contract, you might choose to panic in drop when
panic in drop when the resource has not been properly closed. the resource has not been properly closed. This can help catch contract
This can help catch contract violations at runtime. violations at runtime.
This is one situation where drop bombs are useful, This is one situation where drop bombs are useful, which we will discuss next.
which we will discuss next.
</details> </details>

View File

@ -1,13 +1,18 @@
# Scope Guards # Scope Guards
A scope guard makes use of the `Drop` trait A scope guard makes use of the `Drop` trait to run a given closure when it goes
to run a given closure when it goes out of scope. out of scope.
```rust ```rust
use std::{io::Write, fs::{self, File}}; use scopeguard::{ScopeGuard, guard};
use scopeguard::{guard, ScopeGuard}; use std::{
fs::{self, File},
io::Write,
};
fn conditional_success() -> bool { true } fn conditional_success() -> bool {
true
}
fn main() { fn main() {
let path = "temp.txt"; let path = "temp.txt";
@ -35,12 +40,12 @@ fn main() {
<details> <details>
- This example demonstrates the use of - This example demonstrates the use of
[the `scopeguard` crate](https://docs.rs/scopeguard/latest/scopeguard/), [the `scopeguard` crate](https://docs.rs/scopeguard/latest/scopeguard/), which
which is commonly used in internal APIs to ensure that a closure runs is commonly used in internal APIs to ensure that a closure runs when a scope
when a scope exits. exits.
- If the cleanup logic in the example above were unconditional, - If the cleanup logic in the example above were unconditional, the code could
the code could be simplified using be simplified using
[scopeguard's `defer!` macro](https://docs.rs/scopeguard/latest/scopeguard/#defer): [scopeguard's `defer!` macro](https://docs.rs/scopeguard/latest/scopeguard/#defer):
```rust ```rust
@ -51,8 +56,8 @@ fn main() {
} }
``` ```
- If desired, the "scope guard" pattern can be implemented manually, - If desired, the "scope guard" pattern can be implemented manually, starting as
starting as follows: follows:
```rust ```rust
struct ScopeGuard<T, F: FnOnce()> { struct ScopeGuard<T, F: FnOnce()> {
@ -98,33 +103,32 @@ fn main() {
} }
``` ```
- The `ScopeGuard` type in the `scopeguard` crate also includes - The `ScopeGuard` type in the `scopeguard` crate also includes a `Debug`
a `Debug` implementation and a third parameter: implementation and a third parameter: a
a [`Strategy`](https://docs.rs/scopeguard/latest/scopeguard/trait.Strategy.html) [`Strategy`](https://docs.rs/scopeguard/latest/scopeguard/trait.Strategy.html)
that determines when the `drop_fn` should run. that determines when the `drop_fn` should run.
- By default, the strategy runs the drop function unconditionally. - By default, the strategy runs the drop function unconditionally. However,
However, the crate also provides built-in strategies to run the drop function the crate also provides built-in strategies to run the drop function only
only during unwinding (due to a panic), or only on successful scope exit. during unwinding (due to a panic), or only on successful scope exit.
You can also implement your own `Strategy` trait You can also implement your own `Strategy` trait to define custom
to define custom conditions for when the cleanup should occur. conditions for when the cleanup should occur.
- Remark also that the crates' `ScopeGuard` makes use of - Remark also that the crates' `ScopeGuard` makes use of
[`ManuallyDrop`](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html) [`ManuallyDrop`](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html)
instead of `Option` to avoid automatic or premature dropping instead of `Option` to avoid automatic or premature dropping of values,
of values, giving precise manual control and preventing giving precise manual control and preventing double-drops. This avoids the
double-drops. This avoids the runtime overhead and semantic ambiguity that comes with using Option. runtime overhead and semantic ambiguity that comes with using Option.
- Recalling the transaction example from - Recalling the transaction example from
[the drop bombs chapter](./drop_bomb.md), [the drop bombs chapter](./drop_bomb.md), we can now combine both concepts:
we can now combine both concepts: define a fallback that runs unless we explicitly abort early. In the success
define a fallback that runs unless we explicitly abort early. path, we call `ScopeGuard::into_inner` to prevent the rollback, as the
In the success path, we call `ScopeGuard::into_inner` transaction has already been committed.
to prevent the rollback, as the transaction has already been committed.
While we still cannot propagate errors from fallible operations inside the drop logic, While we still cannot propagate errors from fallible operations inside the
this pattern at least allows us to orchestrate fallbacks explicitly drop logic, this pattern at least allows us to orchestrate fallbacks
and with whatever guarantees or limits we require. explicitly and with whatever guarantees or limits we require.
</details> </details>