Skip to main content

async_safe_defer/
sync.rs

1use core::{
2    fmt,
3    marker::PhantomData,
4    mem,
5    ops::{Deref, DerefMut},
6};
7
8/// Decides whether a deferred action runs when its guard is dropped.
9pub trait Strategy {
10    /// Returns `true` when the action should run in the current drop context.
11    fn should_run() -> bool;
12}
13
14/// Selects the action whenever an armed guard is dropped.
15#[derive(Debug)]
16pub enum Always {}
17
18impl Strategy for Always {
19    #[inline(always)]
20    fn should_run() -> bool {
21        true
22    }
23}
24
25/// Selects the action when an armed guard is dropped outside panic unwinding.
26///
27/// Returning `Result::Err` is a successful exit for this policy because it does
28/// not inspect return values.
29#[cfg(feature = "std")]
30#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
31#[derive(Debug)]
32pub enum OnSuccess {}
33
34#[cfg(feature = "std")]
35impl Strategy for OnSuccess {
36    #[inline]
37    fn should_run() -> bool {
38        !std::thread::panicking()
39    }
40}
41
42/// Selects the action when an armed guard is dropped during panic unwinding.
43#[cfg(feature = "std")]
44#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
45#[derive(Debug)]
46pub enum OnUnwind {}
47
48#[cfg(feature = "std")]
49impl Strategy for OnUnwind {
50    #[inline]
51    fn should_run() -> bool {
52        std::thread::panicking()
53    }
54}
55
56/// Holds a synchronous action for at-most-once deferred execution.
57///
58/// The strategy decides whether the action runs when the guard is dropped.
59/// Calling [`DeferGuard::disarm`] returns the action without executing it, while
60/// [`DeferGuard::run_now`] executes it immediately regardless of the strategy.
61/// Panics from the strategy or a drop-time action propagate. A second panic
62/// during unwinding may abort the process.
63#[must_use = "store the guard so its deferred action runs at the intended scope exit"]
64pub struct DeferGuard<F: FnOnce(), S: Strategy = Always> {
65    action: Option<F>,
66    strategy: PhantomData<fn() -> S>,
67}
68
69impl<F: FnOnce()> DeferGuard<F> {
70    /// Arms `action` with the [`Always`] strategy.
71    #[inline]
72    pub const fn new(action: F) -> Self {
73        Self::with_strategy(action)
74    }
75}
76
77impl<F: FnOnce(), S: Strategy> DeferGuard<F, S> {
78    /// Arms `action` with strategy `S`.
79    #[inline]
80    pub const fn with_strategy(action: F) -> Self {
81        Self {
82            action: Some(action),
83            strategy: PhantomData,
84        }
85    }
86
87    /// Disarms the guard and returns its action without executing it.
88    #[inline]
89    pub fn disarm(mut self) -> F {
90        self.action
91            .take()
92            .expect("an armed defer guard always contains its action")
93    }
94
95    /// Executes the action immediately, regardless of `S`, and consumes the guard.
96    #[inline]
97    pub fn run_now(mut self) {
98        if let Some(action) = self.action.take() {
99            action();
100        }
101    }
102}
103
104impl<F: FnOnce(), S: Strategy> Drop for DeferGuard<F, S> {
105    #[inline]
106    fn drop(&mut self) {
107        let Some(action) = self.action.take() else {
108            return;
109        };
110
111        if S::should_run() {
112            action();
113        }
114    }
115}
116
117impl<F: FnOnce(), S: Strategy> fmt::Debug for DeferGuard<F, S> {
118    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119        formatter
120            .debug_struct("DeferGuard")
121            .field("armed", &self.action.is_some())
122            .finish()
123    }
124}
125
126enum ScopeState<T, F> {
127    Armed { value: T, action: F },
128    Disarmed,
129}
130
131/// Owns a value and conditionally passes it to an action when dropped.
132///
133/// The guarded value is accessible through [`Deref`] and [`DerefMut`]. The
134/// action receives ownership of the final value and runs at most once.
135/// Panics from the strategy or a drop-time action propagate. A second panic
136/// during unwinding may abort the process.
137#[must_use = "store the guard so its deferred action runs at the intended scope exit"]
138pub struct ScopeGuard<T, F, S = Always>
139where
140    F: FnOnce(T),
141    S: Strategy,
142{
143    state: ScopeState<T, F>,
144    strategy: PhantomData<fn() -> S>,
145}
146
147impl<T, F, S> ScopeGuard<T, F, S>
148where
149    F: FnOnce(T),
150    S: Strategy,
151{
152    /// Creates an armed guard using strategy `S`.
153    #[inline]
154    pub const fn with_strategy(value: T, action: F) -> Self {
155        Self {
156            state: ScopeState::Armed { value, action },
157            strategy: PhantomData,
158        }
159    }
160
161    /// Disarms the guard and returns its value without running the action.
162    #[inline]
163    pub fn into_inner(mut self) -> T {
164        let (value, action) = match mem::replace(&mut self.state, ScopeState::Disarmed) {
165            ScopeState::Armed { value, action } => (value, action),
166            ScopeState::Disarmed => unreachable!("a live scope guard is armed"),
167        };
168        drop(action);
169        value
170    }
171
172    /// Disarms the guard and returns both its value and action.
173    #[inline]
174    pub fn into_parts(mut self) -> (T, F) {
175        match mem::replace(&mut self.state, ScopeState::Disarmed) {
176            ScopeState::Armed { value, action } => (value, action),
177            ScopeState::Disarmed => unreachable!("a live scope guard is armed"),
178        }
179    }
180
181    /// Runs the action immediately, regardless of `S`, and consumes the guard.
182    #[inline]
183    pub fn run_now(mut self) {
184        if let ScopeState::Armed { value, action } =
185            mem::replace(&mut self.state, ScopeState::Disarmed)
186        {
187            action(value);
188        }
189    }
190
191    fn value(&self) -> &T {
192        match &self.state {
193            ScopeState::Armed { value, .. } => value,
194            ScopeState::Disarmed => unreachable!("a live scope guard is armed"),
195        }
196    }
197
198    fn value_mut(&mut self) -> &mut T {
199        match &mut self.state {
200            ScopeState::Armed { value, .. } => value,
201            ScopeState::Disarmed => unreachable!("a live scope guard is armed"),
202        }
203    }
204}
205
206impl<T, F, S> Deref for ScopeGuard<T, F, S>
207where
208    F: FnOnce(T),
209    S: Strategy,
210{
211    type Target = T;
212
213    #[inline]
214    fn deref(&self) -> &Self::Target {
215        self.value()
216    }
217}
218
219impl<T, F, S> DerefMut for ScopeGuard<T, F, S>
220where
221    F: FnOnce(T),
222    S: Strategy,
223{
224    #[inline]
225    fn deref_mut(&mut self) -> &mut Self::Target {
226        self.value_mut()
227    }
228}
229
230impl<T, F, S> Drop for ScopeGuard<T, F, S>
231where
232    F: FnOnce(T),
233    S: Strategy,
234{
235    #[inline]
236    fn drop(&mut self) {
237        let ScopeState::Armed { value, action } =
238            mem::replace(&mut self.state, ScopeState::Disarmed)
239        else {
240            return;
241        };
242
243        if S::should_run() {
244            action(value);
245        }
246    }
247}
248
249impl<T, F, S> fmt::Debug for ScopeGuard<T, F, S>
250where
251    T: fmt::Debug,
252    F: FnOnce(T),
253    S: Strategy,
254{
255    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
256        formatter
257            .debug_struct("ScopeGuard")
258            .field("value", self.value())
259            .finish()
260    }
261}
262
263/// Arms a new [`DeferGuard`] with the [`Always`] strategy.
264#[inline]
265pub const fn defer<F: FnOnce()>(action: F) -> DeferGuard<F> {
266    DeferGuard::new(action)
267}
268
269/// Arms a new [`DeferGuard`] that runs on a non-panicking drop.
270#[cfg(feature = "std")]
271#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
272#[inline]
273pub const fn defer_on_success<F: FnOnce()>(action: F) -> DeferGuard<F, OnSuccess> {
274    DeferGuard::with_strategy(action)
275}
276
277/// Arms a new [`DeferGuard`] that runs while unwinding from a panic.
278#[cfg(feature = "std")]
279#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
280#[inline]
281pub const fn defer_on_unwind<F: FnOnce()>(action: F) -> DeferGuard<F, OnUnwind> {
282    DeferGuard::with_strategy(action)
283}
284
285/// Owns `value` and passes it to `action` when the guard is dropped.
286#[inline]
287pub const fn guard<T, F>(value: T, action: F) -> ScopeGuard<T, F>
288where
289    F: FnOnce(T),
290{
291    ScopeGuard::with_strategy(value, action)
292}
293
294/// Owns `value` and passes it to `action` on a non-panicking drop.
295#[cfg(feature = "std")]
296#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
297#[inline]
298pub const fn guard_on_success<T, F>(value: T, action: F) -> ScopeGuard<T, F, OnSuccess>
299where
300    F: FnOnce(T),
301{
302    ScopeGuard::with_strategy(value, action)
303}
304
305/// Owns `value` and passes it to `action` while unwinding from a panic.
306#[cfg(feature = "std")]
307#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
308#[inline]
309pub const fn guard_on_unwind<T, F>(value: T, action: F) -> ScopeGuard<T, F, OnUnwind>
310where
311    F: FnOnce(T),
312{
313    ScopeGuard::with_strategy(value, action)
314}
315
316/// Binds a deferred action that runs on a non-panicking scope exit.
317///
318/// The action must evaluate to `()`.
319#[cfg(feature = "std")]
320#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
321#[macro_export]
322macro_rules! defer_on_success {
323    (move $($body:tt)*) => {
324        let _defer_on_success_guard = $crate::defer_on_success(move || { $($body)* });
325    };
326    ($($body:tt)*) => {
327        let _defer_on_success_guard = $crate::defer_on_success(|| { $($body)* });
328    };
329}
330
331/// Binds a deferred action that runs while unwinding from a panic.
332///
333/// The action must evaluate to `()`. If it panics, the second panic may abort
334/// the process.
335#[cfg(feature = "std")]
336#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
337#[macro_export]
338macro_rules! defer_on_unwind {
339    (move $($body:tt)*) => {
340        let _defer_on_unwind_guard = $crate::defer_on_unwind(move || { $($body)* });
341    };
342    ($($body:tt)*) => {
343        let _defer_on_unwind_guard = $crate::defer_on_unwind(|| { $($body)* });
344    };
345}