Skip to main content

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}