async_dropx/
lib.rs

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