async_dropx/
lib.rs

1use std::ops::{Deref, DerefMut};
2use std::future::Future;
3
4/// Trait for types that require async cleanup.
5///
6/// Implement this trait for your type to define how it should be cleaned up asynchronously.
7pub trait AsyncDrop {
8    /// The future returned by `async_drop`.
9    type Dropper: Future<Output = ()> + Send + 'static;
10
11    /// Perform the async cleanup.
12    /// This method consumes the object.
13    fn async_drop(self) -> Self::Dropper;
14}
15
16/// Wrapper that ensures `async_drop` is called when the object goes out of scope.
17///
18/// This wrapper implements `Deref` and `DerefMut`, so you can use it just like the inner type.
19/// When it goes out of scope, `Drop` is called, which takes the inner value and spawns
20/// the future returned by `async_drop` on the active async runtime.
21///
22/// Supported runtimes:
23/// - `tokio` (requires `tokio` feature)
24/// - `async-std` (requires `async-std` feature)
25pub struct AsyncDropx<T: AsyncDrop + Send + 'static> {
26    inner: Option<T>,
27}
28
29impl<T: AsyncDrop + Send + 'static> AsyncDropx<T> {
30    /// Create a new `AsyncDropx` wrapping the given value.
31    pub fn new(inner: T) -> Self {
32        Self { inner: Some(inner) }
33    }
34}
35
36impl<T: AsyncDrop + Send + 'static> Deref for AsyncDropx<T> {
37    type Target = T;
38
39    fn deref(&self) -> &Self::Target {
40        self.inner.as_ref().expect("Inner value is missing - this should never happen unless already dropped")
41    }
42}
43
44impl<T: AsyncDrop + Send + 'static> DerefMut for AsyncDropx<T> {
45    fn deref_mut(&mut self) -> &mut Self::Target {
46        self.inner.as_mut().expect("Inner value is missing - this should never happen unless already dropped")
47    }
48}
49
50impl<T: AsyncDrop + Send + 'static> Drop for AsyncDropx<T> {
51    fn drop(&mut self) {
52        if let Some(inner) = self.inner.take() {
53            let _future = inner.async_drop();
54            
55            #[cfg(feature = "tokio")]
56            {
57                if let Ok(handle) = tokio::runtime::Handle::try_current() {
58                    handle.spawn(_future);
59                    return;
60                }
61            }
62            
63            #[cfg(feature = "async-std")]
64            {
65                // async-std doesn't strictly require a handle check, but we can just spawn it.
66                // However, if we are not in a runtime, this might panic or fail?
67                // async_std::task::spawn usually works if the runtime is initialized.
68                async_std::task::spawn(_future);
69                return;
70            }
71
72            // If we reach here, we couldn't spawn the task.
73            // This might happen if:
74            // 1. No feature flags are enabled.
75            // 2. Tokio feature is enabled but we are not in a Tokio context.
76            // 3. Async-std feature is enabled but something went wrong (though async-std is global).
77            
78            #[cfg(not(any(feature = "tokio", feature = "async-std")))]
79            {
80                eprintln!("AsyncDropx: No async runtime feature enabled (tokio/async-std). Cleanup leaked.");
81            }
82            
83            #[cfg(any(feature = "tokio", feature = "async-std"))]
84            {
85                 eprintln!("AsyncDropx: Failed to spawn async cleanup task. Runtime might be missing or shut down.");
86            }
87        }
88    }
89}