run_on_drop/
lib.rs

1//! This crate provides a closure wrapper that will run the closure when it is dropped.
2//!
3//! See [`on_drop`] for examples.
4
5#![no_std]
6
7#[cfg(test)]
8extern crate alloc;
9
10use core::mem::ManuallyDrop;
11
12/// Creates an object that will run the closure when it is dropped.
13///
14/// The closure will run even if the object is dropped during unwinding.
15///
16/// Running the closure can be cancelled by calling [`OnDrop::forget`].
17///
18/// # Example: Object setup without RAII
19///
20/// ```
21/// use run_on_drop::on_drop;
22///
23/// let object = create_object();
24/// let cleanup = on_drop(|| destroy_object(&object));
25/// initialize_object(&object); // might unwind
26/// cleanup.forget();
27/// return object;
28/// #
29/// # fn create_object() { }
30/// # fn destroy_object(_: &()) { }
31/// # fn initialize_object(_: &()) { }
32/// ```
33///
34/// # Example: Resetting a flag
35///
36/// ```
37/// use core::cell::Cell;
38/// use run_on_drop::on_drop;
39///
40/// let flag = Cell::new(false);
41///
42/// flag.set(true);
43/// let _reset_flag = on_drop(|| flag.set(false));
44/// f();
45/// #
46/// # fn f() { }
47/// ```
48#[inline(always)]
49pub fn on_drop<F>(f: F) -> OnDrop<F>
50where
51    F: FnOnce(),
52{
53    OnDrop(ManuallyDrop::new(f))
54}
55
56/// A type that runs the contained closure when it is dropped.
57///
58/// This type is constructed with the [`on_drop`] function.
59pub struct OnDrop<F>(ManuallyDrop<F>)
60where
61    F: FnOnce();
62
63// SAFETY: OnDrop does not provide shared access to the `F`.
64unsafe impl<F> Sync for OnDrop<F> where F: FnOnce() {}
65
66impl<F> OnDrop<F>
67where
68    F: FnOnce(),
69{
70    /// Forgets this `OnDrop` without leaking memory.
71    ///
72    /// The contained closure will be dropped without being run.
73    #[inline(always)]
74    pub fn forget(self) {
75        let mut slf = ManuallyDrop::new(self);
76        // SAFETY: Since slf is ManuallyDrop, the drop impl will not run.
77        let _f = unsafe { ManuallyDrop::take(&mut slf.0) };
78    }
79}
80
81impl<F> Drop for OnDrop<F>
82where
83    F: FnOnce(),
84{
85    #[inline(always)]
86    fn drop(&mut self) {
87        // SAFETY: This is the drop impl so no other code will access self.0.
88        let f = unsafe { ManuallyDrop::take(&mut self.0) };
89        f();
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use {crate::on_drop, alloc::boxed::Box, core::cell::Cell};
96
97    #[test]
98    fn drop() {
99        let mut dropped = Box::new(0);
100        {
101            on_drop(|| *dropped += 1);
102        }
103        assert_eq!(*dropped, 1);
104    }
105
106    #[test]
107    fn forget() {
108        let mut dropped = Box::new(0);
109        {
110            on_drop(|| *dropped += 1).forget();
111        }
112        assert_eq!(*dropped, 0);
113    }
114
115    #[test]
116    fn double_drop() {
117        let dropped = Box::new(Cell::new(0));
118        {
119            let f = Box::new(on_drop(|| dropped.set(dropped.get() + 1)));
120            let dropped = &dropped;
121            on_drop(move || {
122                let _v = f;
123                dropped.set(dropped.get() + 1);
124            });
125        }
126        assert_eq!(dropped.get(), 2);
127    }
128
129    #[test]
130    fn double_drop_forget() {
131        let dropped = Box::new(Cell::new(0));
132        {
133            let f = Box::new(on_drop(|| dropped.set(dropped.get() + 1)));
134            on_drop(move || {
135                let _v = f;
136            })
137            .forget();
138        }
139        assert_eq!(dropped.get(), 1);
140    }
141
142    #[test]
143    #[should_panic(expected = "explicit panic")]
144    fn panic_in_double_drop_forget() {
145        let dropped = Box::new(Cell::new(0));
146        let _assert_n = on_drop(|| assert_eq!(dropped.get(), 0));
147        {
148            let f = Box::new(on_drop(|| panic!()));
149            let dropped = &dropped;
150            on_drop(move || {
151                let _v = f;
152                dropped.set(dropped.get() + 1);
153            })
154            .forget();
155        }
156    }
157}