run-on-drop 1.0.0

Run code when an object is dropped
Documentation
//! This crate provides a closure wrapper that will run the closure when it is dropped.
//!
//! See [`on_drop`] for examples.

#![no_std]

#[cfg(test)]
extern crate alloc;

use core::mem::ManuallyDrop;

/// Creates an object that will run the closure when it is dropped.
///
/// The closure will run even if the object is dropped during unwinding.
///
/// Running the closure can be cancelled by calling [`OnDrop::forget`].
///
/// # Example: Object setup without RAII
///
/// ```
/// use run_on_drop::on_drop;
///
/// let object = create_object();
/// let cleanup = on_drop(|| destroy_object(&object));
/// initialize_object(&object); // might unwind
/// cleanup.forget();
/// return object;
/// #
/// # fn create_object() { }
/// # fn destroy_object(_: &()) { }
/// # fn initialize_object(_: &()) { }
/// ```
///
/// # Example: Resetting a flag
///
/// ```
/// use core::cell::Cell;
/// use run_on_drop::on_drop;
///
/// let flag = Cell::new(false);
///
/// flag.set(true);
/// let _reset_flag = on_drop(|| flag.set(false));
/// f();
/// #
/// # fn f() { }
/// ```
#[inline(always)]
pub fn on_drop<F>(f: F) -> OnDrop<F>
where
    F: FnOnce(),
{
    OnDrop(ManuallyDrop::new(f))
}

/// A type that runs the contained closure when it is dropped.
///
/// This type is constructed with the [`on_drop`] function.
pub struct OnDrop<F>(ManuallyDrop<F>)
where
    F: FnOnce();

// SAFETY: OnDrop does not provide shared access to the `F`.
unsafe impl<F> Sync for OnDrop<F> where F: FnOnce() {}

impl<F> OnDrop<F>
where
    F: FnOnce(),
{
    /// Forgets this `OnDrop` without leaking memory.
    ///
    /// The contained closure will be dropped without being run.
    #[inline(always)]
    pub fn forget(self) {
        let mut slf = ManuallyDrop::new(self);
        // SAFETY: Since slf is ManuallyDrop, the drop impl will not run.
        let _f = unsafe { ManuallyDrop::take(&mut slf.0) };
    }
}

impl<F> Drop for OnDrop<F>
where
    F: FnOnce(),
{
    #[inline(always)]
    fn drop(&mut self) {
        // SAFETY: This is the drop impl so no other code will access self.0.
        let f = unsafe { ManuallyDrop::take(&mut self.0) };
        f();
    }
}

#[cfg(test)]
mod tests {
    use {crate::on_drop, alloc::boxed::Box, core::cell::Cell};

    #[test]
    fn drop() {
        let mut dropped = Box::new(0);
        {
            on_drop(|| *dropped += 1);
        }
        assert_eq!(*dropped, 1);
    }

    #[test]
    fn forget() {
        let mut dropped = Box::new(0);
        {
            on_drop(|| *dropped += 1).forget();
        }
        assert_eq!(*dropped, 0);
    }

    #[test]
    fn double_drop() {
        let dropped = Box::new(Cell::new(0));
        {
            let f = Box::new(on_drop(|| dropped.set(dropped.get() + 1)));
            let dropped = &dropped;
            on_drop(move || {
                let _v = f;
                dropped.set(dropped.get() + 1);
            });
        }
        assert_eq!(dropped.get(), 2);
    }

    #[test]
    fn double_drop_forget() {
        let dropped = Box::new(Cell::new(0));
        {
            let f = Box::new(on_drop(|| dropped.set(dropped.get() + 1)));
            on_drop(move || {
                let _v = f;
            })
            .forget();
        }
        assert_eq!(dropped.get(), 1);
    }

    #[test]
    #[should_panic(expected = "explicit panic")]
    fn panic_in_double_drop_forget() {
        let dropped = Box::new(Cell::new(0));
        let _assert_n = on_drop(|| assert_eq!(dropped.get(), 0));
        {
            let f = Box::new(on_drop(|| panic!()));
            let dropped = &dropped;
            on_drop(move || {
                let _v = f;
                dropped.set(dropped.get() + 1);
            })
            .forget();
        }
    }
}