You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-08-08 08:22:52 +02:00
apply dprint fmt
This commit is contained in:
@ -441,6 +441,7 @@
|
||||
- [Drop Limitations](idiomatic/leveraging-the-type-system/raii/drop_limitations.md)
|
||||
- [Drop Bomb](idiomatic/leveraging-the-type-system/raii/drop_bomb.md)
|
||||
- [Scope Guards](idiomatic/leveraging-the-type-system/raii/scope_guards.md)
|
||||
|
||||
---
|
||||
|
||||
# Final Words
|
||||
|
@ -4,11 +4,11 @@ minutes: 30
|
||||
|
||||
# RAII and `Drop` in Practice
|
||||
|
||||
RAII (*Resource Acquisition Is Initialization*)
|
||||
means tying the lifetime of a resource to the lifetime of a value.
|
||||
RAII (_Resource Acquisition Is Initialization_) means tying the lifetime of a
|
||||
resource to the lifetime of a value.
|
||||
|
||||
Rust applies RAII automatically for memory management.
|
||||
The `Drop` trait lets you extend this pattern to anything else.
|
||||
Rust applies RAII automatically for memory management. The `Drop` trait lets you
|
||||
extend this pattern to anything else.
|
||||
|
||||
```rust
|
||||
use std::sync::Mutex;
|
||||
@ -26,28 +26,31 @@ fn main() {
|
||||
<details>
|
||||
|
||||
- In the above example
|
||||
[the `Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html)
|
||||
owns its data: you can’t access the value inside without first acquiring the lock.
|
||||
[the `Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) owns its
|
||||
data: you can’t access the value inside without first acquiring the lock.
|
||||
|
||||
`mux.lock()` returns a
|
||||
[`MutexGuard`](https://doc.rust-lang.org/std/sync/struct.MutexGuard.html),
|
||||
which [dereferences](https://doc.rust-lang.org/std/ops/trait.DerefMut.html)
|
||||
to the data and implements [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html).
|
||||
which [dereferences](https://doc.rust-lang.org/std/ops/trait.DerefMut.html) to
|
||||
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)
|
||||
that the [`Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html)
|
||||
lets you define what should happen when a resource is dropped.
|
||||
- You may recall from
|
||||
[the Memory Management chapter](../../memory-management/drop.md) that the
|
||||
[`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),
|
||||
we saw the most common situation where a resource is dropped:
|
||||
when the scope of its _owner_ ends at the boundary of a block (`{}`).
|
||||
- In
|
||||
[the Blocks and Scopes chapter](../../control-flow-basics/blocks-and-scopes.md),
|
||||
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
|
||||
[`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.
|
||||
|
||||
- There are also other scenarios where this can happen,
|
||||
such as when the value owning the resource is "shadowed" by another value:
|
||||
- There are also other scenarios where this can happen, such as when the value
|
||||
owning the resource is "shadowed" by another value:
|
||||
|
||||
```rust
|
||||
let a = String::from("foo");
|
||||
@ -55,62 +58,59 @@ fn main() {
|
||||
// because we shadow its binding with a new value.
|
||||
```
|
||||
|
||||
- Recall also from [the Drop chapter](../../memory-management/drop.md)
|
||||
that for a composite type such as a `struct`, all its fields will be dropped
|
||||
when the struct itself is dropped.
|
||||
If a field implements the `Drop` trait, its `Drop::drop`
|
||||
_trait_ method will also be invoked.
|
||||
- Recall also from [the Drop chapter](../../memory-management/drop.md) that
|
||||
for a composite type such as a `struct`, all its fields will be dropped when
|
||||
the struct itself is dropped. If a field implements the `Drop` trait, its
|
||||
`Drop::drop` _trait_ method will also be invoked.
|
||||
|
||||
- In any scenario where the stack unwinds the value, it is guaranteed
|
||||
that the [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop)
|
||||
- In any scenario where the stack unwinds the value, it is guaranteed that the
|
||||
[`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop)
|
||||
method of a value `a` will be called.
|
||||
|
||||
- This holds true for happy paths such as:
|
||||
|
||||
- Exiting a block or function scope.
|
||||
|
||||
- Returning early with an explicit `return` statement,
|
||||
or implicitly by using
|
||||
[the Try operator (`?`)](../../error-handling/try.md)
|
||||
to early-return `Option` or `Result` values.
|
||||
- Returning early with an explicit `return` statement, or implicitly by
|
||||
using [the Try operator (`?`)](../../error-handling/try.md) to
|
||||
early-return `Option` or `Result` values.
|
||||
|
||||
- It also holds for unexpected scenarios where a `panic` is triggered, if:
|
||||
|
||||
- The stack unwinds on panic (which is the default),
|
||||
allowing for graceful cleanup of resources.
|
||||
- The stack unwinds on panic (which is the default), allowing for graceful
|
||||
cleanup of resources.
|
||||
|
||||
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).
|
||||
|
||||
- No panic occurs within any of the `drop` methods
|
||||
invoked before reaching the `drop` call of the object `a`.
|
||||
- No panic occurs within any of the `drop` methods invoked before reaching
|
||||
the `drop` call of the object `a`.
|
||||
|
||||
- Note that
|
||||
[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.
|
||||
In other words, the stack is not unwound in this case,
|
||||
and the `drop` method will not be called.
|
||||
as sometimes used in CLI tools, terminates the process immediately. In other
|
||||
words, the stack is not unwound in this case, and the `drop` method will not
|
||||
be called.
|
||||
|
||||
- `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.
|
||||
|
||||
In contrast to C++ or Java, where you often have to unlock manually
|
||||
or use a `lock/unlock` pattern, Rust ensures the
|
||||
lock *cannot* be forgotten, thanks to the compiler.
|
||||
In contrast to C++ or Java, where you often have to unlock manually or use a
|
||||
`lock/unlock` pattern, Rust ensures the lock _cannot_ be forgotten, thanks to
|
||||
the compiler.
|
||||
|
||||
- In other scenarios, the `Drop` trait shows its limitations.
|
||||
Next, we'll look at what those are and how we can
|
||||
address them.
|
||||
- In other scenarios, the `Drop` trait shows its limitations. Next, we'll look
|
||||
at what those are and how we can address them.
|
||||
|
||||
## More to explore
|
||||
|
||||
To learn more about building synchronization primitives,
|
||||
consider reading [*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`.
|
||||
To learn more about building synchronization primitives, consider reading
|
||||
[_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`.
|
||||
|
||||
</details>
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Drop Bombs: Enforcing API Correctness
|
||||
|
||||
Use `Drop` to enforce invariants and detect incorrect API usage.
|
||||
A "drop bomb" panics if not defused.
|
||||
Use `Drop` to enforce invariants and detect incorrect API usage. A "drop bomb"
|
||||
panics if not defused.
|
||||
|
||||
```rust
|
||||
struct Transaction {
|
||||
@ -35,28 +35,28 @@ impl Drop for Transaction {
|
||||
|
||||
<details>
|
||||
|
||||
- The example above uses the drop bomb pattern to enforce at runtime that a transaction
|
||||
is never dropped in an unfinished state. This applies to cases such as a database
|
||||
transaction that remains active in an external system.
|
||||
- The example above uses the drop bomb pattern to enforce at runtime that a
|
||||
transaction is never dropped in an unfinished state. This applies to cases
|
||||
such as a database transaction that remains active in an external system.
|
||||
|
||||
In this example, the programmer must finalize the transaction explicitly,
|
||||
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
|
||||
to ensure that manually allocated memory from the guest language is cleaned up through
|
||||
an explicit call to a safe API function.
|
||||
- In the context of FFI, where cross-boundary references are involved, it is
|
||||
often necessary to ensure that manually allocated memory from the guest
|
||||
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
|
||||
are clearly documented under a Panic section. This helps ensure that users of the API
|
||||
are aware of the consequences of misuse.
|
||||
- Similar to unsafe code, it is recommended that APIs with expectations like
|
||||
these are clearly documented under a Panic section. This helps ensure that
|
||||
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,
|
||||
without placing implicit runtime obligations on library users.
|
||||
|
||||
- If there is a way to restore the system to a valid state using a fallback
|
||||
in the Drop implementation, it is advisable to restrict the use of drop bombs
|
||||
to Debug mode. In Release mode, the Drop implementation could fall back to
|
||||
safe cleanup logic while still logging the incident as an error.
|
||||
- If there is a way to restore the system to a valid state using a fallback in
|
||||
the Drop implementation, it is advisable to restrict the use of drop bombs to
|
||||
Debug mode. In Release mode, the Drop implementation could fall back to safe
|
||||
cleanup logic while still logging the incident as an error.
|
||||
|
||||
- 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,
|
||||
making manual cleanup required and explicit.
|
||||
|
||||
- The [`drop_bomb` crate](https://docs.rs/drop_bomb/latest/drop_bomb/)
|
||||
provides a way to enforce that certain values are not dropped unless explicitly defused.
|
||||
It can be added to an existing struct and exposes a `.defuse()` method to make dropping safe.
|
||||
The crate also includes a `DebugDropBomb` variant for use in debug-only builds.
|
||||
- The [`drop_bomb` crate](https://docs.rs/drop_bomb/latest/drop_bomb/) provides
|
||||
a way to enforce that certain values are not dropped unless explicitly
|
||||
defused. It can be added to an existing struct and exposes a `.defuse()`
|
||||
method to make dropping safe. The crate also includes a `DebugDropBomb`
|
||||
variant for use in debug-only builds.
|
||||
|
||||
## More to explore
|
||||
|
||||
Rust does not currently support full linear types or typestate programming
|
||||
in the core language. This means the compiler cannot guarantee that a resource
|
||||
was used exactly once or finalized before being dropped.
|
||||
Rust does not currently support full linear types or typestate programming in
|
||||
the core language. This means the compiler cannot guarantee that a resource was
|
||||
used exactly once or finalized before being dropped.
|
||||
|
||||
Drop bombs serve as a runtime mechanism to enforce such usage invariants manually.
|
||||
This is typically done in a Drop implementation that panics if a required method,
|
||||
such as `.commit()`, was not called before the value went out of scope.
|
||||
Drop bombs serve as a runtime mechanism to enforce such usage invariants
|
||||
manually. This is typically done in a Drop implementation that panics if a
|
||||
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:
|
||||
<https://github.com/rust-lang/rfcs/issues/814>.
|
||||
|
@ -1,8 +1,7 @@
|
||||
# The limitations of `Drop`
|
||||
|
||||
While `Drop` works well for cases
|
||||
like synchronization primitives, its use becomes more
|
||||
questionable when dealing with I/O or unsafe resources.
|
||||
While `Drop` works well for cases like synchronization primitives, its use
|
||||
becomes more questionable when dealing with I/O or unsafe resources.
|
||||
|
||||
```rust
|
||||
use std::fs::File;
|
||||
@ -19,17 +18,17 @@ fn write_log() -> io::Result<()> {
|
||||
|
||||
<details>
|
||||
|
||||
- In the earlier example, our `File` resource owns a file handle
|
||||
provided by the operating system.
|
||||
- In the earlier example, our `File` resource owns a file handle provided by the
|
||||
operating system.
|
||||
|
||||
[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.
|
||||
> Errors detected on closing are ignored by the implementation of Drop.
|
||||
> Files are automatically closed when they go out of scope. Errors detected on
|
||||
> closing are ignored by the implementation of Drop.
|
||||
|
||||
- This highlights a key limitation of the `Drop` trait:
|
||||
it cannot propagate errors to the caller. In other words,
|
||||
fallible cleanup logic cannot be handled by the code using the `File`.
|
||||
- This highlights a key limitation of the `Drop` trait: it cannot propagate
|
||||
errors to the caller. In other words, fallible cleanup logic cannot be handled
|
||||
by the code using the `File`.
|
||||
|
||||
This becomes clear when looking at the
|
||||
[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
|
||||
cannot be surfaced or recovered from. This is by design:
|
||||
`drop` is invoked automatically when a value is popped off the stack during
|
||||
unwinding, leaving no opportunity for error handling.
|
||||
cannot be surfaced or recovered from. This is by design: `drop` is invoked
|
||||
automatically when a value is popped off the stack during unwinding, leaving
|
||||
no opportunity for error handling.
|
||||
|
||||
- One workaround is to panic inside `drop` when a failure occurs.
|
||||
However, this is risky—if a panic happens while the stack is already unwinding,
|
||||
the program will abort immediately, and remaining resources will not be cleaned up.
|
||||
- One workaround is to panic inside `drop` when a failure occurs. However, this
|
||||
is risky—if a panic happens while the stack is already unwinding, the program
|
||||
will abort immediately, and remaining resources will not be cleaned up.
|
||||
|
||||
While panicking in `drop` can serve certain purposes (see
|
||||
[the next chapter on "drop bombs"](./drop_bomb.md)), it should be used sparingly
|
||||
and with full awareness of the consequences.
|
||||
[the next chapter on "drop bombs"](./drop_bomb.md)), it should be used
|
||||
sparingly and with full awareness of the consequences.
|
||||
|
||||
- Another drawback of `drop` is that its execution is implicit and non-deterministic
|
||||
in terms of timing. You cannot control *when* a value is dropped. And in fact as
|
||||
discussed in previous slide it might never even run at all, leaving the external
|
||||
resource in an undefined state.
|
||||
- Another drawback of `drop` is that its execution is implicit and
|
||||
non-deterministic in terms of timing. You cannot control _when_ a value is
|
||||
dropped. And in fact as discussed in previous slide it might never even run at
|
||||
all, leaving the external resource in an undefined state.
|
||||
|
||||
This matters particularly for I/O: normally you might set a timeout on blocking
|
||||
operations, but when I/O occurs in a `drop` implementation, you have no way to
|
||||
enforce such constraints.
|
||||
This matters particularly for I/O: normally you might set a timeout on
|
||||
blocking operations, but when I/O occurs in a `drop` implementation, you have
|
||||
no way to enforce such constraints.
|
||||
|
||||
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.
|
||||
Since the call to `drop` happens implicitly and outside your control,
|
||||
there's no way to apply a timeout or fallback mechanism.
|
||||
due to OS-level buffering or locking), the drop operation could block
|
||||
indefinitely. Since the call to `drop` happens implicitly and outside your
|
||||
control, there's no way to apply a timeout or fallback mechanism.
|
||||
|
||||
- For smart pointers and synchronization primitives, none of these drawbacks matter,
|
||||
since the operations are nearly instant and a program panic does not cause undefined behavior.
|
||||
The poisoned state disappears along with the termination of the program.
|
||||
- For smart pointers and synchronization primitives, none of these drawbacks
|
||||
matter, since the operations are nearly instant and a program panic does not
|
||||
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
|
||||
clean up resources explicitly using a close function.
|
||||
- For use cases such as I/O or FFI, it may be preferable to let the user clean
|
||||
up resources explicitly using a close function.
|
||||
|
||||
However, this approach cannot be enforced at the type level.
|
||||
If explicit cleanup is part of your API contract, you might choose to
|
||||
panic in drop when the resource has not been properly closed.
|
||||
This can help catch contract violations at runtime.
|
||||
However, this approach cannot be enforced at the type level. If explicit
|
||||
cleanup is part of your API contract, you might choose to panic in drop when
|
||||
the resource has not been properly closed. This can help catch contract
|
||||
violations at runtime.
|
||||
|
||||
This is one situation where drop bombs are useful,
|
||||
which we will discuss next.
|
||||
This is one situation where drop bombs are useful, which we will discuss next.
|
||||
|
||||
</details>
|
||||
|
@ -1,13 +1,18 @@
|
||||
# Scope Guards
|
||||
|
||||
A scope guard makes use of the `Drop` trait
|
||||
to run a given closure when it goes out of scope.
|
||||
A scope guard makes use of the `Drop` trait to run a given closure when it goes
|
||||
out of scope.
|
||||
|
||||
```rust
|
||||
use std::{io::Write, fs::{self, File}};
|
||||
use scopeguard::{guard, ScopeGuard};
|
||||
use scopeguard::{ScopeGuard, guard};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
};
|
||||
|
||||
fn conditional_success() -> bool { true }
|
||||
fn conditional_success() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let path = "temp.txt";
|
||||
@ -35,12 +40,12 @@ fn main() {
|
||||
<details>
|
||||
|
||||
- This example demonstrates the use of
|
||||
[the `scopeguard` crate](https://docs.rs/scopeguard/latest/scopeguard/),
|
||||
which is commonly used in internal APIs to ensure that a closure runs
|
||||
when a scope exits.
|
||||
[the `scopeguard` crate](https://docs.rs/scopeguard/latest/scopeguard/), which
|
||||
is commonly used in internal APIs to ensure that a closure runs when a scope
|
||||
exits.
|
||||
|
||||
- If the cleanup logic in the example above were unconditional,
|
||||
the code could be simplified using
|
||||
- If the cleanup logic in the example above were unconditional, the code could
|
||||
be simplified using
|
||||
[scopeguard's `defer!` macro](https://docs.rs/scopeguard/latest/scopeguard/#defer):
|
||||
|
||||
```rust
|
||||
@ -51,8 +56,8 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
- If desired, the "scope guard" pattern can be implemented manually,
|
||||
starting as follows:
|
||||
- If desired, the "scope guard" pattern can be implemented manually, starting as
|
||||
follows:
|
||||
|
||||
```rust
|
||||
struct ScopeGuard<T, F: FnOnce()> {
|
||||
@ -98,33 +103,32 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
- The `ScopeGuard` type in the `scopeguard` crate also includes
|
||||
a `Debug` implementation and a third parameter:
|
||||
a [`Strategy`](https://docs.rs/scopeguard/latest/scopeguard/trait.Strategy.html)
|
||||
- The `ScopeGuard` type in the `scopeguard` crate also includes a `Debug`
|
||||
implementation and a third parameter: a
|
||||
[`Strategy`](https://docs.rs/scopeguard/latest/scopeguard/trait.Strategy.html)
|
||||
that determines when the `drop_fn` should run.
|
||||
|
||||
- By default, the strategy runs the drop function unconditionally.
|
||||
However, the crate also provides built-in strategies to run the drop function
|
||||
only during unwinding (due to a panic), or only on successful scope exit.
|
||||
- By default, the strategy runs the drop function unconditionally. However,
|
||||
the crate also provides built-in strategies to run the drop function only
|
||||
during unwinding (due to a panic), or only on successful scope exit.
|
||||
|
||||
You can also implement your own `Strategy` trait
|
||||
to define custom conditions for when the cleanup should occur.
|
||||
You can also implement your own `Strategy` trait to define custom
|
||||
conditions for when the cleanup should occur.
|
||||
|
||||
- Remark also that the crates' `ScopeGuard` makes use of
|
||||
[`ManuallyDrop`](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html)
|
||||
instead of `Option` to avoid automatic or premature dropping
|
||||
of values, giving precise manual control and preventing
|
||||
double-drops. This avoids the runtime overhead and semantic ambiguity that comes with using Option.
|
||||
instead of `Option` to avoid automatic or premature dropping of values,
|
||||
giving precise manual control and preventing double-drops. This avoids the
|
||||
runtime overhead and semantic ambiguity that comes with using Option.
|
||||
|
||||
- Recalling the transaction example from
|
||||
[the drop bombs chapter](./drop_bomb.md),
|
||||
we can now combine both concepts:
|
||||
define a fallback that runs unless we explicitly abort early.
|
||||
In the success path, we call `ScopeGuard::into_inner`
|
||||
to prevent the rollback, as the transaction has already been committed.
|
||||
[the drop bombs chapter](./drop_bomb.md), we can now combine both concepts:
|
||||
define a fallback that runs unless we explicitly abort early. In the success
|
||||
path, we call `ScopeGuard::into_inner` to prevent the rollback, as the
|
||||
transaction has already been committed.
|
||||
|
||||
While we still cannot propagate errors from fallible operations inside the drop logic,
|
||||
this pattern at least allows us to orchestrate fallbacks explicitly
|
||||
and with whatever guarantees or limits we require.
|
||||
While we still cannot propagate errors from fallible operations inside the
|
||||
drop logic, this pattern at least allows us to orchestrate fallbacks
|
||||
explicitly and with whatever guarantees or limits we require.
|
||||
|
||||
</details>
|
||||
|
Reference in New Issue
Block a user