async-drop
Guide to the AsyncDrop pattern for async cleanup in Rust. Use when working with AsyncDropGuard, implementing AsyncDrop trait, or handling async resource cleanup.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install cryfs-cryfs-async-drop
Repository
Skill path: .claude/skills/async-drop
Guide to the AsyncDrop pattern for async cleanup in Rust. Use when working with AsyncDropGuard, implementing AsyncDrop trait, or handling async resource cleanup.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: cryfs.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install async-drop into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/cryfs/cryfs before adding async-drop to shared team environments
- Use async-drop for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: async-drop
description: Guide to the AsyncDrop pattern for async cleanup in Rust. Use when working with AsyncDropGuard, implementing AsyncDrop trait, or handling async resource cleanup.
---
# AsyncDrop Pattern Guide
The AsyncDrop pattern enables async cleanup for types that hold resources requiring asynchronous teardown (network connections, file handles, background tasks, etc.).
## Core Concept
Rust's `Drop` trait is synchronous, but sometimes cleanup needs to be async. The AsyncDrop pattern solves this by:
1. Wrapping values in `AsyncDropGuard<T>`
2. Requiring explicit `async_drop().await` calls
3. Panicking if cleanup is forgotten
## Quick Reference
```rust
// Creating
let mut guard = AsyncDropGuard::new(my_value);
// Using (transparent via Deref)
guard.do_something();
// Cleanup (REQUIRED before dropping)
guard.async_drop().await?;
```
## The AsyncDrop Trait
```rust
#[async_trait]
pub trait AsyncDrop {
type Error: Debug;
async fn async_drop_impl(&mut self) -> Result<(), Self::Error>;
}
```
## Essential Rules
| Rule | Description |
|------|-------------|
| **Always call async_drop()** | Every `AsyncDropGuard` must have `async_drop()` called |
| **Factory methods return guards** | `fn new() -> AsyncDropGuard<Self>`, never plain `Self` |
| **Types with guard members impl AsyncDrop** | Delegate to member async_drops |
| **Use the macro when possible** | `with_async_drop_2!` handles cleanup automatically |
| **Panics are exceptions** | It's OK to skip async_drop on panic paths |
## The `with_async_drop_2!` Macro
Automatically calls `async_drop()` on scope exit:
```rust
let resource = get_resource().await?;
with_async_drop_2!(resource, {
// Use resource here
resource.do_work().await?;
Ok(result)
})
```
## Additional References
- [patterns.md](patterns.md) - Implementation patterns and examples
- [gotchas.md](gotchas.md) - Common mistakes and how to avoid them
- [helpers.md](helpers.md) - Helper types (AsyncDropArc, AsyncDropHashMap, etc.)
## Location
Implementation: `crates/utils/src/async_drop/`
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### patterns.md
```markdown
# AsyncDrop Patterns
Common patterns for implementing and using AsyncDrop in this codebase.
## Pattern 1: Simple AsyncDrop Implementation
For types that need async cleanup:
```rust
use async_trait::async_trait;
use cryfs_utils::{AsyncDrop, AsyncDropGuard};
pub struct MyResource {
connection: Connection,
}
impl MyResource {
// Factory returns AsyncDropGuard, not Self
pub fn new(connection: Connection) -> AsyncDropGuard<Self> {
AsyncDropGuard::new(Self { connection })
}
}
#[async_trait]
impl AsyncDrop for MyResource {
type Error = anyhow::Error;
async fn async_drop_impl(&mut self) -> Result<(), Self::Error> {
self.connection.close().await?;
Ok(())
}
}
```
## Pattern 2: Delegating to Member AsyncDrops
When a type contains `AsyncDropGuard` members:
```rust
pub struct CompositeResource {
database: AsyncDropGuard<Database>,
cache: AsyncDropGuard<Cache>,
}
#[async_trait]
impl AsyncDrop for CompositeResource {
type Error = anyhow::Error;
async fn async_drop_impl(&mut self) -> Result<(), Self::Error> {
// Drop members in appropriate order
self.cache.async_drop().await?;
self.database.async_drop().await?;
Ok(())
}
}
```
## Pattern 3: Using `with_async_drop_2!` Macro
The preferred approach when it fits - automatically handles cleanup:
```rust
use cryfs_utils::with_async_drop_2;
async fn process_file(path: &Path) -> Result<Data> {
let file = open_file(path).await?; // Returns AsyncDropGuard<File>
with_async_drop_2!(file, {
// Use file here
let data = file.read_all().await?;
process(data).await
})
// file.async_drop() is called automatically
}
```
### Macro Variants
```rust
// Basic - propagates async_drop errors as-is
with_async_drop_2!(value, {
// ... work ...
Ok(result)
})
// With error mapping - converts async_drop errors
with_async_drop_2!(value, {
// ... work ...
Ok(result)
}, MyError::from)
// Infallible - for types with Error = Never
with_async_drop_2_infallible!(value, {
// ... work ...
result
})
```
## Pattern 4: Manual Cleanup on All Exit Paths
When the macro doesn't fit, manually ensure cleanup on every path:
```rust
async fn complex_operation(resource: AsyncDropGuard<Resource>) -> Result<Output> {
let mut resource = resource;
// Early return path 1
if !resource.is_valid() {
resource.async_drop().await?;
return Err(Error::Invalid);
}
// Main work
let result = match resource.process().await {
Ok(data) => data,
Err(e) => {
resource.async_drop().await?; // Don't forget!
return Err(e.into());
}
};
// Success path
resource.async_drop().await?;
Ok(result)
}
```
## Pattern 5: Internal Unwrapping with `unsafe_into_inner_dont_drop()`
Use `unsafe_into_inner_dont_drop()` internally within a type to access the inner value when the type itself handles cleanup via its own `AsyncDrop` implementation.
```rust
pub struct Wrapper {
inner: AsyncDropGuard<Resource>,
}
impl Wrapper {
/// Consumes the wrapper to perform an operation on the inner resource.
/// The Wrapper's AsyncDrop handles cleanup of the inner resource.
pub async fn consume(this: AsyncDropGuard<Self>) -> Result<Output> {
// Unwrap Self from its guard - we're inside our own impl
let mut this = this.unsafe_into_inner_dont_drop();
// Now we can work with this.inner directly
let result = this.inner.do_something().await?;
// We MUST still clean up inner - our responsibility hasn't changed
this.inner.async_drop().await?;
Ok(result)
}
}
#[async_trait]
impl AsyncDrop for Wrapper {
type Error = anyhow::Error;
async fn async_drop_impl(&mut self) -> Result<(), Self::Error> {
self.inner.async_drop().await?;
Ok(())
}
}
```
**Key point:** `unsafe_into_inner_dont_drop()` unwraps `Self` from its `AsyncDropGuard`, but the type's own `AsyncDrop` impl (or explicit cleanup in the consuming method) is still responsible for cleaning up members. This does NOT transfer responsibility elsewhere.
## Pattern 6: Conditional AsyncDrop with Newtype Wrapper
For types with multiple states (like enums), wrap in a newtype to prevent direct instantiation:
```rust
// Private enum - cannot be constructed outside this module
enum MaybeInitializedInner<T> {
Uninitialized(Option<Box<dyn FnOnce() -> AsyncDropGuard<T>>>),
Initialized(AsyncDropGuard<T>),
}
// Public newtype - only way to create is via factory methods returning AsyncDropGuard
pub struct MaybeInitialized<T>(MaybeInitializedInner<T>);
impl<T> MaybeInitialized<T> {
// Factory methods return AsyncDropGuard<Self>, never Self
pub fn uninitialized(factory: impl FnOnce() -> AsyncDropGuard<T> + 'static) -> AsyncDropGuard<Self> {
AsyncDropGuard::new(Self(MaybeInitializedInner::Uninitialized(Some(Box::new(factory)))))
}
pub fn initialized(value: AsyncDropGuard<T>) -> AsyncDropGuard<Self> {
AsyncDropGuard::new(Self(MaybeInitializedInner::Initialized(value)))
}
}
#[async_trait]
impl<T: AsyncDrop + Debug + Send> AsyncDrop for MaybeInitialized<T> {
type Error = T::Error;
async fn async_drop_impl(&mut self) -> Result<(), Self::Error> {
match &mut self.0 {
MaybeInitializedInner::Uninitialized(factory) => {
if let Some(factory) = factory.take() {
factory().async_drop().await?;
}
}
MaybeInitializedInner::Initialized(value) => {
value.async_drop().await?;
}
}
Ok(())
}
}
```
**Key point:** The inner enum is private, so callers cannot construct `MaybeInitialized` directly - they must use factory methods that return `AsyncDropGuard<Self>`.
## Pattern 7: Passing Guards by Value
When passing `AsyncDropGuard<T>` by value, ownership and cleanup responsibility transfers:
```rust
// Caller is responsible for cleanup
async fn caller() -> Result<()> {
let mut resource = create_resource();
process_resource(resource).await?; // Transfers ownership
// No need to call async_drop - process_resource owns it now
Ok(())
}
// Callee takes ownership, must clean up
async fn process_resource(mut resource: AsyncDropGuard<Resource>) -> Result<()> {
resource.do_work().await?;
resource.async_drop().await?; // Callee's responsibility
Ok(())
}
```
## Pattern 8: Returning Guards
When returning a guard, caller receives cleanup responsibility:
```rust
async fn create_and_configure() -> Result<AsyncDropGuard<Resource>> {
let mut resource = Resource::new(); // Returns AsyncDropGuard
resource.configure().await?;
Ok(resource) // Caller must async_drop
}
async fn use_it() -> Result<()> {
let mut resource = create_and_configure().await?;
resource.work().await?;
resource.async_drop().await?; // Our responsibility now
Ok(())
}
```
## Pattern 9: Parallel Cleanup with AsyncDropHashMap
For collections of async-droppable values:
```rust
use cryfs_utils::AsyncDropHashMap;
let mut map: AsyncDropHashMap<String, Connection> = AsyncDropHashMap::new();
map.insert("db1".to_string(), Connection::new("db1").await?);
map.insert("db2".to_string(), Connection::new("db2").await?);
// All values are dropped in parallel
map.async_drop().await?;
```
## Pattern 10: Concurrent Cleanup for Independent Members
When a type has multiple independent members, drop them concurrently for better performance:
```rust
pub struct ConnectionPool {
conn_a: AsyncDropGuard<Connection>,
conn_b: AsyncDropGuard<Connection>,
conn_c: AsyncDropGuard<Connection>,
}
#[async_trait]
impl AsyncDrop for ConnectionPool {
type Error = anyhow::Error;
async fn async_drop_impl(&mut self) -> Result<(), Self::Error> {
// GOOD - concurrent drop for independent resources
let (a, b, c) = tokio::join!(
self.conn_a.async_drop(),
self.conn_b.async_drop(),
self.conn_c.async_drop()
);
a?;
b?;
c?;
Ok(())
}
}
```
Use `tokio::join!` to run async_drop calls concurrently when members don't depend on each other.
## Pattern 11: Shared Ownership with AsyncDropArc
When multiple owners need access:
```rust
use cryfs_utils::AsyncDropArc;
let shared = AsyncDropArc::new(AsyncDropGuard::new(resource));
let clone1 = AsyncDropArc::clone(&shared);
let clone2 = AsyncDropArc::clone(&shared);
// All clones must be dropped
clone1.async_drop().await?;
clone2.async_drop().await?;
shared.async_drop().await?; // Last one does actual cleanup
```
## Pattern 12: Error Type Selection
Choose error types based on context:
```rust
// Specific error for library types
#[async_trait]
impl AsyncDrop for DatabaseConnection {
type Error = DatabaseError; // Specific, detailed
// ...
}
// Anyhow for application-level types
#[async_trait]
impl AsyncDrop for AppResource {
type Error = anyhow::Error; // Flexible
// ...
}
// Never for infallible cleanup
#[async_trait]
impl AsyncDrop for SimpleBuffer {
type Error = std::convert::Infallible;
async fn async_drop_impl(&mut self) -> Result<(), Self::Error> {
self.data.clear(); // Can't fail
Ok(())
}
}
```
## Anti-Pattern: Forgetting Cleanup in Error Paths
```rust
// WRONG - leaks resource on error
async fn bad_example(mut resource: AsyncDropGuard<R>) -> Result<()> {
resource.step1().await?; // If this fails, resource leaks!
resource.async_drop().await?;
Ok(())
}
// RIGHT - cleanup on all paths
async fn good_example(mut resource: AsyncDropGuard<R>) -> Result<()> {
let result = resource.step1().await;
resource.async_drop().await?;
result?;
Ok(())
}
// BETTER - use the macro
async fn best_example(resource: AsyncDropGuard<R>) -> Result<()> {
with_async_drop_2!(resource, {
resource.step1().await
})
}
```
```
### gotchas.md
```markdown
# AsyncDrop Gotchas
Common mistakes and pitfalls when using the AsyncDrop pattern.
## Gotcha 1: Forgetting to Call `async_drop()`
The most common mistake. Will cause a panic at runtime.
```rust
// WRONG - panics with "Forgot to call async_drop on ..."
async fn bad() -> Result<()> {
let resource = Resource::new(); // Returns AsyncDropGuard
resource.do_work().await?;
Ok(())
// Panic here! async_drop was never called
}
// RIGHT
async fn good() -> Result<()> {
let mut resource = Resource::new();
resource.do_work().await?;
resource.async_drop().await?;
Ok(())
}
// BETTER - use the macro
async fn better() -> Result<()> {
let resource = Resource::new();
with_async_drop_2!(resource, {
resource.do_work().await
})
}
```
## Gotcha 2: Missing Cleanup on Early Returns
Every exit path needs cleanup, not just the happy path.
```rust
// WRONG - leaks on early return
async fn bad(mut resource: AsyncDropGuard<R>) -> Result<Data> {
if !resource.is_valid() {
return Err(Error::Invalid); // Leaked!
}
let data = resource.fetch().await?; // Leaked on error!
resource.async_drop().await?;
Ok(data)
}
// RIGHT - cleanup on all paths
async fn good(mut resource: AsyncDropGuard<R>) -> Result<Data> {
if !resource.is_valid() {
resource.async_drop().await?;
return Err(Error::Invalid);
}
let result = resource.fetch().await;
resource.async_drop().await?;
result
}
```
## Gotcha 3: Allowing Direct Instantiation of AsyncDrop Types
AsyncDrop types must prevent callers from creating instances without an `AsyncDropGuard` wrapper. This applies to:
- Factory methods (must return `AsyncDropGuard<Self>`)
- Struct visibility (fields should be private)
- Enum variants (use newtype wrapper)
```rust
// WRONG - factory returns plain Self
impl MyType {
pub fn new() -> Self {
Self { /* ... */ }
}
}
// WRONG - public fields allow direct construction
pub struct MyType {
pub field: String, // Caller can do: MyType { field: "".into() }
}
// WRONG - public enum variants allow direct construction
pub enum State {
Ready(AsyncDropGuard<Resource>), // Caller can do: State::Ready(...)
}
// RIGHT - private fields, factory returns guard
pub struct MyType {
field: String, // Private
}
impl MyType {
pub fn new(field: String) -> AsyncDropGuard<Self> {
AsyncDropGuard::new(Self { field })
}
}
// RIGHT - newtype wrapper with private inner enum
enum StateInner {
Ready(AsyncDropGuard<Resource>),
}
pub struct State(StateInner); // Inner is private
impl State {
pub fn ready(resource: AsyncDropGuard<Resource>) -> AsyncDropGuard<Self> {
AsyncDropGuard::new(Self(StateInner::Ready(resource)))
}
}
```
**Principle:** If callers can construct `Self` directly, they can bypass the `AsyncDropGuard` and cause panics or resource leaks.
## Gotcha 4: Types with Guard Members Not Implementing AsyncDrop
If a type holds `AsyncDropGuard` members, it must implement `AsyncDrop`.
```rust
// WRONG - inner guards never get async_drop called
pub struct Container {
inner: AsyncDropGuard<Resource>,
}
impl Container {
pub fn new() -> AsyncDropGuard<Self> {
AsyncDropGuard::new(Self {
inner: Resource::new(),
})
}
}
// Container's async_drop doesn't call inner.async_drop()!
// RIGHT
#[async_trait]
impl AsyncDrop for Container {
type Error = <Resource as AsyncDrop>::Error;
async fn async_drop_impl(&mut self) -> Result<(), Self::Error> {
self.inner.async_drop().await
}
}
```
## Gotcha 5: Misusing `unsafe_into_inner_dont_drop()`
This method is for internal use within a type to unwrap `Self` from its guard. The type is still responsible for cleaning up its members.
```rust
// WRONG - using it to avoid cleanup
let guard = AsyncDropGuard::new(resource);
let inner = guard.unsafe_into_inner_dont_drop();
// inner is now unguarded - if you don't clean up members, they leak!
// WRONG - passing unwrapped value externally without cleanup
impl MyType {
pub async fn bad(this: AsyncDropGuard<Self>) -> Result<()> {
let inner = this.unsafe_into_inner_dont_drop();
external_function(inner).await // Who cleans up inner's members?
}
}
// RIGHT - internal unwrapping, still clean up members
impl MyType {
pub async fn good(this: AsyncDropGuard<Self>) -> Result<()> {
let mut inner = this.unsafe_into_inner_dont_drop();
let result = inner.member.do_work().await?;
inner.member.async_drop().await?; // Still our responsibility!
Ok(result)
}
}
```
## Gotcha 6: Using SyncDrop in Production
`SyncDrop` calls `async_drop()` synchronously in its `Drop` impl. This can deadlock.
```rust
// DANGEROUS - can deadlock on single-thread runtime
let guard = AsyncDropGuard::new(resource);
let sync = SyncDrop::new(guard);
drop(sync); // Calls block_on(async_drop()) - may deadlock!
```
`SyncDrop` is intended for test utilities only. In production, always use async cleanup.
## Gotcha 7: Wrong Drop Order for Dependent Members
When members have dependencies, drop in correct order (usually reverse of construction).
```rust
pub struct System {
cache: AsyncDropGuard<Cache>, // Uses database
database: AsyncDropGuard<Database>, // Independent
}
#[async_trait]
impl AsyncDrop for System {
type Error = anyhow::Error;
async fn async_drop_impl(&mut self) -> Result<(), Self::Error> {
// WRONG order - database closes while cache still uses it
// self.database.async_drop().await?;
// self.cache.async_drop().await?;
// RIGHT order - close cache first, then database
self.cache.async_drop().await?;
self.database.async_drop().await?;
Ok(())
}
}
```
For independent members, see Pattern 10 (Concurrent Cleanup) in patterns.md.
## Gotcha 8: Panics and AsyncDrop
Panics skip `async_drop()`. This is acceptable - panics are treated as unrecoverable.
```rust
async fn may_panic(mut resource: AsyncDropGuard<R>) -> Result<()> {
resource.do_work().await?;
// If this panics, resource.async_drop() is skipped
// This is OK - panics are unrecoverable errors
some_operation_that_may_panic();
resource.async_drop().await?;
Ok(())
}
```
Don't try to call `async_drop()` in panic handlers.
## Gotcha 9: Double async_drop
Calling `async_drop()` twice is harmless but wasteful - it returns `Ok(())` on second call.
```rust
let mut resource = Resource::new();
resource.async_drop().await?; // Does cleanup
resource.async_drop().await?; // No-op, returns Ok(())
```
Use `is_dropped()` to check if already dropped if needed:
```rust
if !resource.is_dropped() {
resource.async_drop().await?;
}
```
## Gotcha 10: Holding Guards Across Await Points Without Cleanup
Long-lived guards in loops need careful handling.
```rust
// WRONG - accumulates guards without cleanup
async fn bad_loop() -> Result<()> {
loop {
let resource = Resource::new();
resource.process().await?;
// Guard dropped here - PANIC!
}
}
// RIGHT - cleanup each iteration
async fn good_loop() -> Result<()> {
loop {
let mut resource = Resource::new();
resource.process().await?;
resource.async_drop().await?;
}
}
```
## Gotcha 11: Clone vs AsyncDropArc
Regular `Clone` on `AsyncDropGuard` is not available. Use `AsyncDropArc` for shared ownership.
```rust
// WRONG - AsyncDropGuard doesn't implement Clone
let guard = AsyncDropGuard::new(resource);
let clone = guard.clone(); // Compile error!
// RIGHT - use AsyncDropArc for sharing
let shared = AsyncDropArc::new(AsyncDropGuard::new(resource));
let clone = AsyncDropArc::clone(&shared);
// Both must be async_dropped, last one does actual cleanup
```
## Gotcha 12: Forgetting `mut` Binding
`async_drop()` takes `&mut self`, so the guard must be mutable.
```rust
// WRONG - can't call async_drop on immutable binding
let resource = Resource::new();
resource.async_drop().await?; // Error: cannot borrow as mutable
// RIGHT
let mut resource = Resource::new();
resource.async_drop().await?;
```
## Summary Checklist
Before submitting code with AsyncDrop:
- [ ] Every `AsyncDropGuard` has `async_drop()` called
- [ ] All error paths call `async_drop()` before returning
- [ ] All early returns call `async_drop()` first
- [ ] Factory methods return `AsyncDropGuard<Self>`
- [ ] Direct instantiation prevented (private fields, newtype wrappers for enums)
- [ ] Types with guard members implement `AsyncDrop`
- [ ] Guard bindings are `mut`
- [ ] `unsafe_into_inner_dont_drop()` only used internally, with member cleanup handled
- [ ] Drop order correct for dependent members (reverse of construction)
- [ ] Independent members dropped concurrently (Pattern 10)
- [ ] Using `with_async_drop_2!` where possible
```
### helpers.md
```markdown
# AsyncDrop Helper Types
Wrapper types and utilities for common AsyncDrop scenarios.
## AsyncDropGuard<T>
The core wrapper that enforces async cleanup.
```rust
pub struct AsyncDropGuard<T: Debug>(Option<T>);
```
### Key Methods
| Method | Description |
|--------|-------------|
| `new(v: T)` | Wrap a value |
| `async_drop(&mut self)` | Perform async cleanup (required!) |
| `is_dropped(&self)` | Check if already dropped |
| `unsafe_into_inner_dont_drop(self)` | Extract inner, bypassing cleanup |
| `map_unsafe<U>(self, f)` | Transform inner type |
### Behavior
- Implements `Deref` and `DerefMut` for transparent access
- Has `#[must_use]` attribute
- Panics in `Drop` if `async_drop()` was not called
```rust
let mut guard = AsyncDropGuard::new(value);
guard.method(); // Deref to inner
guard.async_drop().await?;
```
---
## AsyncDropArc<T>
Reference-counted sharing of async-droppable values.
```rust
pub struct AsyncDropArc<T: AsyncDrop + Debug + Send> {
v: Option<Arc<AsyncDropGuard<T>>>,
}
```
### Use Case
Multiple owners need access to the same resource. Only the last owner's `async_drop()` performs actual cleanup.
### Usage
```rust
let shared = AsyncDropArc::new(AsyncDropGuard::new(resource));
// Clone for multiple owners
let clone1 = AsyncDropArc::clone(&shared);
let clone2 = AsyncDropArc::clone(&shared);
// All must be dropped
clone1.async_drop().await?; // No-op (not last)
clone2.async_drop().await?; // No-op (not last)
shared.async_drop().await?; // Actual cleanup (last Arc)
```
### Key Methods
| Method | Description |
|--------|-------------|
| `new(guard)` | Wrap an AsyncDropGuard |
| `clone(&self)` | Create another reference |
| `async_drop(&mut self)` | Drop this reference (cleanup on last) |
---
## AsyncDropTokioMutex<T>
Async mutex holding an async-droppable value.
```rust
pub struct AsyncDropTokioMutex<T: AsyncDrop + Debug + Send> {
v: Option<Mutex<AsyncDropGuard<T>>>,
}
```
### Use Case
Safe concurrent access to an `AsyncDropGuard` value via async mutex.
### Usage
```rust
let mutex = AsyncDropTokioMutex::new(AsyncDropGuard::new(resource));
// Access via lock
{
let mut guard = mutex.lock().await;
guard.do_work().await?;
}
// Cleanup
mutex.async_drop().await?;
```
---
## AsyncDropHashMap<K, V>
HashMap that properly cleans up async-droppable values.
```rust
pub struct AsyncDropHashMap<K, V>
where
K: PartialEq + Eq + Hash + Debug + Send,
V: AsyncDrop + Send + Debug,
{
map: HashMap<K, AsyncDropGuard<V>>,
}
```
### Use Case
Collections of resources that all need async cleanup.
### Usage
```rust
let mut map = AsyncDropHashMap::new();
map.insert("conn1".to_string(), Connection::new("db1").await?);
map.insert("conn2".to_string(), Connection::new("db2").await?);
// Access entries
if let Some(conn) = map.get_mut("conn1") {
conn.query().await?;
}
// Cleanup all entries (in parallel!)
map.async_drop().await?;
```
### Key Methods
| Method | Description |
|--------|-------------|
| `new()` | Create empty map |
| `insert(k, v)` | Add entry (v is `AsyncDropGuard<V>`) |
| `get(&k)` | Get reference to value |
| `get_mut(&k)` | Get mutable reference |
| `remove(&k)` | Remove and return entry |
| `async_drop()` | Drop all values in parallel |
---
## AsyncDropResult<T, E>
Wraps `Result<AsyncDropGuard<T>, E>`.
```rust
pub struct AsyncDropResult<T, E>
where
T: Debug + AsyncDrop + Send,
E: Debug + Send,
{
v: Result<AsyncDropGuard<T>, E>,
}
```
### Use Case
When you have a Result that might contain an async-droppable value.
### Behavior
- `async_drop()` only acts on `Ok` variant
- `Err` variant is left untouched
```rust
let result: AsyncDropResult<Resource, Error> = try_create_resource().await;
// Cleanup handles Ok case, ignores Err
result.async_drop().await?;
```
---
## SyncDrop<T>
Wrapper that calls `async_drop()` synchronously in its `Drop` impl.
```rust
pub struct SyncDrop<T: Debug + AsyncDrop>(Option<AsyncDropGuard<T>>);
```
### WARNING: Can Deadlock!
This uses `block_on()` internally, which can deadlock if:
- Running on a single-threaded runtime
- Called from within an async context
### Use Case
**Test utilities only.** When you need synchronous Drop semantics in tests.
```rust
// In tests only!
#[test]
fn test_something() {
let resource = SyncDrop::new(AsyncDropGuard::new(create_resource()));
// Use resource...
// Automatically cleaned up on drop
}
```
DO NOT use SyncDrop outside of tests. It blocks the current thread until
async_drop is complete and will cause bad performance.
---
## Utility Functions
### `with_async_drop()`
Function version of the macro for more complex scenarios.
```rust
pub async fn with_async_drop<T, R, E, F>(
mut value: AsyncDropGuard<T>,
f: impl FnOnce(&mut T) -> F,
) -> Result<R, E>
where
T: AsyncDrop + Debug,
E: From<<T as AsyncDrop>::Error>,
F: Future<Output = Result<R, E>>,
```
### `flatten_async_drop()`
Combines two Results of AsyncDropGuards.
```rust
pub async fn flatten_async_drop<E, T, E1, U, E2>(
first: Result<AsyncDropGuard<T>, E1>,
second: Result<AsyncDropGuard<U>, E2>,
) -> Result<(AsyncDropGuard<T>, AsyncDropGuard<U>), E>
```
Returns tuple of both guards if both are Ok. On error, properly cleans up any successful guard before returning error.
---
## AsyncDropShared<O, Fut>
Advanced: Shares a future that returns an `AsyncDropGuard<O>`.
### Use Case
Multiple tasks await the same resource creation. First to poll drives the future; all get access to the result via `AsyncDropArc`.
```rust
let shared = AsyncDropShared::new(async { create_expensive_resource().await });
// Multiple tasks can await
let clone1 = shared.clone();
let clone2 = shared.clone();
// All get access to the same resource
let result1: AsyncDropArc<Resource> = clone1.await?;
let result2: AsyncDropArc<Resource> = clone2.await?;
```
---
## Choosing the Right Helper
| Scenario | Use |
|----------|-----|
| Single owner, needs async cleanup | `AsyncDropGuard<T>` |
| Multiple owners, shared access | `AsyncDropArc<T>` |
| Concurrent mutable access | `AsyncDropTokioMutex<T>` |
| Collection of resources | `AsyncDropHashMap<K, V>` |
| Result that might need cleanup | `AsyncDropResult<T, E>` |
| Shared future result | `AsyncDropShared<O, Fut>` |
| Test utilities only | `SyncDrop<T>` |
```