nice-catch 0.1.0

Catch your data just before you drop it
Documentation
use std::{
    fmt::{self, Debug, Formatter},
    hash::Hash,
    ops::{Deref, DerefMut},
    pin::Pin,
    task::{self, Poll},
};

use consume_on_drop::{Consume, ConsumeOnDrop};
use derivative::Derivative;
use futures::FutureExt as _;
use pin_project::pin_project;

/// A [`Catch<T>`](Catch) is a [`Deref`]/[`DerefMut`] wrapper around a `T` and a `F: FnOnce(T)`
/// which calls that function on `T` in place of [`drop`].
///
/// This allows you to provide an owned static value with no indirection or locking to somewhere
/// that needs one, and recover the value automatically by a means of your choosing afterwards.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct Catch<'a, T>(ConsumeOnDrop<CatchInner<'a, T>>);

impl<T: Debug> Debug for Catch<'_, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self.0.this)
    }
}

impl<T: Future + Unpin> Future for Catch<'_, T> {
    type Output = T::Output;

    fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
        Pin::new(&mut self.0.this).poll(cx)
    }
}

impl<'a, T: Send + 'a> Catch<'a, T> {
    /// Create a new [`Catch`], which will call the function `catch` on `this` when it is dropped.
    pub fn new(this: T, catch: Box<dyn FnOnce(T) + Send + 'a>) -> Self {
        Catch(ConsumeOnDrop::new(CatchInner { this, catch }))
    }

    /// Disarm a [`Catch`] by unwrapping it into its original `T` and `catch: impl FnOnce(T)`. If
    /// you drop the `T` now, it will be gone forever -- but if you wrap the two together again
    /// using [`Catch::new`] it will again be caught on drop.
    ///
    /// You might use this function if you need owned access to the `T` itself, but beware: if
    /// you're expecting that the `catch` function is always called, you need to manually ensure
    /// that the [`Catch`] is re-assembled or the function called on every code path you care about.
    pub fn disarm(Catch(this): Self) -> (T, Box<dyn FnOnce(T) + 'a>) {
        let CatchInner { this, catch } = ConsumeOnDrop::into_inner(this);
        (this, catch)
    }

    /// Convenience method for the common case when you want to use `Catch` in a synchronous
    /// context, using an internal mutex to hold the caught item.
    ///
    /// The returned closure will yield `None` until the `Catch` is dropped, and then will yield
    /// `Some(T)` exactly once, before yielding `None` again forever.
    pub fn sync(this: T) -> (Self, impl Fn() -> Option<T>) {
        use parking_lot::Mutex;
        use std::sync::Arc;

        // The trick here is that we create a shared mutex `v`:
        let v = Arc::new(Mutex::new(None));

        // When `this` is dropped, we'll move it into `v`:
        let send = {
            let v = v.clone();
            Box::new(move |this| *v.lock() = Some(this))
        };

        // And we can retrieve it afterwards by taking it out of `v`:
        let recv = move || v.lock().take();

        // And we wrap `future` in a "magic" `Catch` that calls `send` on it when it's dropped:
        (Catch::new(this, send), recv)
    }

    /// Convenience method for the common case when you want to use `Catch` in an asynchronous
    /// context, using an internal [`tokio::sync::oneshot`] to hold the caught item.
    ///
    /// The returned future will pend until `this` is dropped, whereupon it will immediately yield
    /// `Some(T)`. If the `Catch` is disarmed and the `catch` function dropped, the future will
    /// yield `None`.
    pub fn future(this: T) -> (Self, impl Future<Output = Option<T>> + Send + 'a) {
        // The trick here is that we create a oneshot channel:
        let (send, recv) = tokio::sync::oneshot::channel();

        // When `this` is dropped, we'll send it on the channel (or drop the original  value if the
        // receiver future is gone):
        let send = Box::new(move |this| send.send(this).unwrap_or_else(drop));

        // And we wrap `future` in a "magic" `Catch` that calls `send` on it when it's dropped:
        (Catch::new(this, send), recv.map(Result::ok))
    }
}

impl<T> Deref for Catch<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0.this
    }
}

impl<T> DerefMut for Catch<'_, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0.this
    }
}

#[derive(Derivative)]
#[derivative(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derivative(
    Debug = "transparent",
    Debug(bound = "T: Debug"),
    Eq(bound = "T: Eq"),
    PartialEq(bound = "T: PartialEq"),
    Ord(bound = "T: Ord"),
    PartialOrd(bound = "T: PartialOrd"),
    Hash(bound = "T: Hash")
)]
#[pin_project]
struct CatchInner<'a, T> {
    this: T,
    #[derivative(
        Debug = "ignore",
        PartialEq = "ignore",
        PartialOrd = "ignore",
        Ord = "ignore",
        Hash = "ignore"
    )]
    catch: Box<dyn FnOnce(T) + Send + 'a>,
}

impl<T> Consume for CatchInner<'_, T> {
    fn consume(self) {
        (self.catch)(self.this)
    }
}