nice_catch/lib.rs
1use std::{
2 fmt::{self, Debug, Formatter},
3 hash::Hash,
4 ops::{Deref, DerefMut},
5 pin::Pin,
6 task::{self, Poll},
7};
8
9use consume_on_drop::{Consume, ConsumeOnDrop};
10use derivative::Derivative;
11use futures::FutureExt as _;
12use pin_project::pin_project;
13
14/// A [`Catch<T>`](Catch) is a [`Deref`]/[`DerefMut`] wrapper around a `T` and a `F: FnOnce(T)`
15/// which calls that function on `T` in place of [`drop`].
16///
17/// This allows you to provide an owned static value with no indirection or locking to somewhere
18/// that needs one, and recover the value automatically by a means of your choosing afterwards.
19#[derive(Eq, PartialEq, Ord, PartialOrd, Hash)]
20#[repr(transparent)]
21pub struct Catch<'a, T>(ConsumeOnDrop<CatchInner<'a, T>>);
22
23impl<T: Debug> Debug for Catch<'_, T> {
24 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
25 write!(f, "{:?}", self.0.this)
26 }
27}
28
29impl<T: Future + Unpin> Future for Catch<'_, T> {
30 type Output = T::Output;
31
32 fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
33 Pin::new(&mut self.0.this).poll(cx)
34 }
35}
36
37impl<'a, T: Send + 'a> Catch<'a, T> {
38 /// Create a new [`Catch`], which will call the function `catch` on `this` when it is dropped.
39 pub fn new(this: T, catch: Box<dyn FnOnce(T) + Send + 'a>) -> Self {
40 Catch(ConsumeOnDrop::new(CatchInner { this, catch }))
41 }
42
43 /// Disarm a [`Catch`] by unwrapping it into its original `T` and `catch: impl FnOnce(T)`. If
44 /// you drop the `T` now, it will be gone forever -- but if you wrap the two together again
45 /// using [`Catch::new`] it will again be caught on drop.
46 ///
47 /// You might use this function if you need owned access to the `T` itself, but beware: if
48 /// you're expecting that the `catch` function is always called, you need to manually ensure
49 /// that the [`Catch`] is re-assembled or the function called on every code path you care about.
50 pub fn disarm(Catch(this): Self) -> (T, Box<dyn FnOnce(T) + 'a>) {
51 let CatchInner { this, catch } = ConsumeOnDrop::into_inner(this);
52 (this, catch)
53 }
54
55 /// Convenience method for the common case when you want to use `Catch` in a synchronous
56 /// context, using an internal mutex to hold the caught item.
57 ///
58 /// The returned closure will yield `None` until the `Catch` is dropped, and then will yield
59 /// `Some(T)` exactly once, before yielding `None` again forever.
60 pub fn sync(this: T) -> (Self, impl Fn() -> Option<T>) {
61 use parking_lot::Mutex;
62 use std::sync::Arc;
63
64 // The trick here is that we create a shared mutex `v`:
65 let v = Arc::new(Mutex::new(None));
66
67 // When `this` is dropped, we'll move it into `v`:
68 let send = {
69 let v = v.clone();
70 Box::new(move |this| *v.lock() = Some(this))
71 };
72
73 // And we can retrieve it afterwards by taking it out of `v`:
74 let recv = move || v.lock().take();
75
76 // And we wrap `future` in a "magic" `Catch` that calls `send` on it when it's dropped:
77 (Catch::new(this, send), recv)
78 }
79
80 /// Convenience method for the common case when you want to use `Catch` in an asynchronous
81 /// context, using an internal [`tokio::sync::oneshot`] to hold the caught item.
82 ///
83 /// The returned future will pend until `this` is dropped, whereupon it will immediately yield
84 /// `Some(T)`. If the `Catch` is disarmed and the `catch` function dropped, the future will
85 /// yield `None`.
86 pub fn future(this: T) -> (Self, impl Future<Output = Option<T>> + Send + 'a) {
87 // The trick here is that we create a oneshot channel:
88 let (send, recv) = tokio::sync::oneshot::channel();
89
90 // When `this` is dropped, we'll send it on the channel (or drop the original value if the
91 // receiver future is gone):
92 let send = Box::new(move |this| send.send(this).unwrap_or_else(drop));
93
94 // And we wrap `future` in a "magic" `Catch` that calls `send` on it when it's dropped:
95 (Catch::new(this, send), recv.map(Result::ok))
96 }
97}
98
99impl<T> Deref for Catch<'_, T> {
100 type Target = T;
101
102 fn deref(&self) -> &Self::Target {
103 &self.0.this
104 }
105}
106
107impl<T> DerefMut for Catch<'_, T> {
108 fn deref_mut(&mut self) -> &mut Self::Target {
109 &mut self.0.this
110 }
111}
112
113#[derive(Derivative)]
114#[derivative(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
115#[derivative(
116 Debug = "transparent",
117 Debug(bound = "T: Debug"),
118 Eq(bound = "T: Eq"),
119 PartialEq(bound = "T: PartialEq"),
120 Ord(bound = "T: Ord"),
121 PartialOrd(bound = "T: PartialOrd"),
122 Hash(bound = "T: Hash")
123)]
124#[pin_project]
125struct CatchInner<'a, T> {
126 this: T,
127 #[derivative(
128 Debug = "ignore",
129 PartialEq = "ignore",
130 PartialOrd = "ignore",
131 Ord = "ignore",
132 Hash = "ignore"
133 )]
134 catch: Box<dyn FnOnce(T) + Send + 'a>,
135}
136
137impl<T> Consume for CatchInner<'_, T> {
138 fn consume(self) {
139 (self.catch)(self.this)
140 }
141}