Back to skills
SkillHub ClubShip Full StackFull Stack

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.

Stars
2,234
Hot score
99
Updated
March 20, 2026
Overall rating
C4.0
Composite score
4.0
Best-practice grade
B75.6

Install command

npx @skill-hub/cli install cryfs-cryfs-async-drop

Repository

cryfs/cryfs

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 repository

Best 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

Claude CodeCodex CLIGemini CLIOpenCode

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>` |

```