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}