Skip to main content

zr/
defer.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// A guard that executes an action when dropped, unless cancelled.
6///
7/// This provides equivalent functionality to C++ `fit::deferred_action` and `fit::defer`.
8#[must_use = "if unused the deferred action will execute immediately"]
9pub struct Deferred<F: FnOnce()> {
10    action: Option<F>,
11}
12
13impl<F: FnOnce()> Deferred<F> {
14    /// Creates a new `Deferred` action that will run when dropped.
15    #[inline]
16    pub const fn new(action: F) -> Self {
17        Self { action: Some(action) }
18    }
19
20    /// Cancels the deferred action so that it will not run upon drop.
21    #[inline]
22    pub fn cancel(&mut self) {
23        self.action = None;
24    }
25
26    /// Executes the deferred action immediately and cancels it.
27    #[inline]
28    pub fn call(&mut self) {
29        if let Some(action) = self.action.take() {
30            action();
31        }
32    }
33}
34
35impl<F: FnOnce()> Drop for Deferred<F> {
36    #[inline]
37    fn drop(&mut self) {
38        if let Some(action) = self.action.take() {
39            action();
40        }
41    }
42}
43
44/// Schedules an action to be executed when the returned guard is dropped.
45///
46/// If the returned guard is dropped, the closure `action` will be executed.
47/// Call [`Deferred::cancel`] on the returned guard to prevent execution.
48#[inline]
49pub fn defer<F: FnOnce()>(action: F) -> Deferred<F> {
50    Deferred::new(action)
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use core::cell::Cell;
57
58    #[test]
59    fn test_defer_runs_on_drop() {
60        let executed = Cell::new(false);
61        {
62            let _guard = defer(|| {
63                executed.set(true);
64            });
65            assert!(!executed.get());
66        }
67        assert!(executed.get());
68    }
69
70    #[test]
71    fn test_defer_cancel() {
72        let executed = Cell::new(false);
73        {
74            let mut guard = defer(|| {
75                executed.set(true);
76            });
77            guard.cancel();
78        }
79        assert!(!executed.get());
80    }
81
82    #[test]
83    fn test_defer_call_early() {
84        let count = Cell::new(0);
85        {
86            let mut guard = defer(|| {
87                count.set(count.get() + 1);
88            });
89            assert_eq!(count.get(), 0);
90            guard.call();
91            assert_eq!(count.get(), 1);
92        }
93        // Should not run again on drop
94        assert_eq!(count.get(), 1);
95    }
96}