async_safe_defer/
lib.rs

1//! This library provides two implementations of RAII-style deferred execution:
2//! one using dynamic allocation (the default) and one that avoids allocation
3//! entirely (`no_alloc`), with a fixed-capacity array of deferred function pointers.
4
5#![cfg_attr(not(test), no_std)]
6
7extern crate alloc;
8use alloc::boxed::Box;
9use alloc::vec::Vec;
10use core::future::Future;
11use core::pin::Pin;
12
13/// RAII-style guard for executing a closure at the end of a scope.
14///
15/// This synchronous variant does not require async. Inspired by Go's `defer`.
16#[must_use = "Defer must be stored in a variable to execute the closure"]
17pub fn defer<F>(f: F) -> impl Drop
18where
19    F: FnOnce(),
20{
21    /// Inner type that holds the closure until drop.
22    struct Defer<F: FnOnce()> {
23        f: Option<F>,
24    }
25
26    impl<F: FnOnce()> Drop for Defer<F> {
27        fn drop(&mut self) {
28            if let Some(f) = self.f.take() {
29                f();
30            }
31        }
32    }
33
34    Defer { f: Some(f) }
35}
36
37/// Macro for creating a synchronous defer guard.
38#[macro_export]
39macro_rules! defer {
40    ($e:expr) => {
41        let _guard = $crate::defer(|| $e);
42        let _ = &_guard;
43    };
44}
45
46/// An async-aware scope guard for storing and running deferred async closures.
47///
48/// This version uses dynamic allocation with `Vec<Box<...>>`. It requires the
49/// global allocator (heap).
50pub struct AsyncScope {
51    defer: Vec<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + 'static>> + 'static>>,
52}
53
54impl AsyncScope {
55    /// Creates a new `AsyncScope` for collecting async deferred tasks.
56    pub fn new() -> Self {
57        AsyncScope { defer: Vec::new() }
58    }
59
60    /// Registers an async closure that will be executed when `run()` is called.
61    ///
62    /// Deferred tasks are run in reverse order (LIFO).
63    pub fn defer<F>(&mut self, f: F)
64    where
65        F: FnOnce() -> Pin<Box<dyn Future<Output = ()> + 'static>> + 'static,
66    {
67        self.defer.push(Box::new(move || Box::pin(f())));
68    }
69
70    /// Executes all stored async tasks in reverse (stack-like) order.
71    pub async fn run(mut self) {
72        while let Some(f) = self.defer.pop() {
73            f().await;
74        }
75    }
76}
77
78/// Macro that creates an async scope to automatically await all async defers.
79///
80/// Inside this block, you can register async cleanup tasks by calling
81/// `scope.defer(...)`, and they will be executed (awaited) at the end of the block
82/// in reverse order.
83#[macro_export]
84macro_rules! async_scope {
85    ($scope:ident, $body:block) => {
86        async {
87            let mut $scope = $crate::AsyncScope::new();
88            $body
89            $scope.run().await;
90        }
91    };
92}
93
94/// A module providing a no-alloc implementation of a fixed-capacity async scope.
95///
96/// This variant does not rely on dynamic allocation. It stores `'static` function
97/// pointers in a fixed-size array and executes them in LIFO order. Each deferred
98/// function pointer must return a `Pin<Box<dyn Future<Output=()> + 'static>>`.
99#[cfg(any(feature = "no_alloc", test))]
100pub mod no_alloc {
101    use alloc::boxed::Box;
102    use core::{future::Future, pin::Pin};
103
104    /// Type alias for a `'static` function pointer returning a pinned async future.
105    pub type DeferredFn = fn() -> Pin<Box<dyn Future<Output = ()> + 'static>>;
106
107    /// A fixed-capacity async scope that does not use dynamic allocation.
108    ///
109    /// - `N` is the maximum number of deferred tasks.
110    /// - Each deferred function must be a `'static` function pointer.
111    pub struct AsyncScopeNoAlloc<const N: usize> {
112        tasks: [Option<DeferredFn>; N],
113        len: usize,
114    }
115
116    impl<const N: usize> AsyncScopeNoAlloc<N> {
117        /// Creates a new `AsyncScopeNoAlloc` with a capacity of `N`.
118        pub const fn new() -> Self {
119            Self {
120                tasks: [None; N],
121                len: 0,
122            }
123        }
124
125        /// Registers a `'static` function pointer to be called later.
126        ///
127        /// Panics if the capacity `N` is exceeded.
128        pub fn defer(&mut self, f: DeferredFn) {
129            if self.len >= N {
130                panic!("No space left for more tasks.");
131            }
132            self.tasks[self.len] = Some(f);
133            self.len += 1;
134        }
135
136        /// Executes all stored tasks in reverse order, awaiting each one.
137        pub async fn run(&mut self) {
138            while self.len > 0 {
139                self.len -= 1;
140                let task = self.tasks[self.len].take().unwrap();
141                (task)().await;
142            }
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    extern crate std;
150    use self::std::sync::{
151        Arc, Mutex,
152        atomic::{AtomicUsize, Ordering},
153    };
154    use super::*;
155
156    #[test]
157    fn test_sync_defer() {
158        println!("test_sync_defer start");
159        let val = Arc::new(AtomicUsize::new(0));
160        {
161            println!("in scope, val={}", val.load(Ordering::SeqCst));
162            let v = val.clone();
163            defer!(v.store(42, Ordering::SeqCst));
164        }
165        println!("out of scope, val={}", val.load(Ordering::SeqCst));
166        assert_eq!(val.load(Ordering::SeqCst), 42);
167    }
168
169    #[tokio::test]
170    async fn test_async_scope_order() {
171        println!("test_async_scope_order start");
172        let log = Arc::new(Mutex::new(Vec::new()));
173        {
174            let mut scope = AsyncScope::new();
175
176            let l1 = log.clone();
177            scope.defer(move || {
178                println!("push(1) scheduled");
179                let l1 = l1.clone();
180                Box::pin(async move {
181                    println!("push(1) running");
182                    l1.lock().unwrap().push(1);
183                })
184            });
185
186            let l2 = log.clone();
187            scope.defer(move || {
188                println!("push(2) scheduled");
189                let l2 = l2.clone();
190                Box::pin(async move {
191                    println!("push(2) running");
192                    l2.lock().unwrap().push(2);
193                })
194            });
195
196            scope.run().await;
197        }
198        let result = log.lock().unwrap().clone();
199        println!("final log: {:?}", result);
200        assert_eq!(result, vec![2, 1]);
201    }
202
203    #[tokio::test]
204    async fn test_async_scope_macro() {
205        println!("test_async_scope_macro start");
206        use crate::async_scope;
207        let flag = Arc::new(AtomicUsize::new(0));
208        {
209            let f = Arc::clone(&flag);
210            async_scope!(scope, {
211                let f2 = Arc::clone(&f);
212                scope.defer(move || {
213                    println!("store(1) scheduled");
214                    Box::pin(async move {
215                        println!("store(1) running");
216                        f2.store(1, Ordering::SeqCst);
217                    })
218                });
219                println!("in scope, flag={}", f.load(Ordering::SeqCst));
220            })
221            .await;
222        }
223        println!("out of scope, flag={}", flag.load(Ordering::SeqCst));
224        assert_eq!(flag.load(Ordering::SeqCst), 1);
225    }
226
227    #[cfg(feature = "no_alloc")]
228    #[tokio::test]
229    async fn test_no_alloc_scope() {
230        println!("test_no_alloc_scope start");
231        use super::no_alloc::{AsyncScopeNoAlloc, DeferredFn};
232        use core::future::Future;
233        use core::pin::Pin;
234
235        fn task_one() -> Pin<Box<dyn Future<Output = ()> + 'static>> {
236            Box::pin(async {
237                println!("task_one running");
238            })
239        }
240        fn task_two() -> Pin<Box<dyn Future<Output = ()> + 'static>> {
241            Box::pin(async {
242                println!("task_two running");
243            })
244        }
245
246        let mut scope = AsyncScopeNoAlloc::<2>::new();
247        scope.defer(task_one as DeferredFn);
248        scope.defer(task_two as DeferredFn);
249        scope.run().await;
250    }
251}