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}