scoped_callback/
lib.rs

1//! Allows registering scoped functions with local borrows with code that expect
2//! functions taking `'static` lifetimes.
3//!
4//! Motivating example:
5//!
6//! ```rust
7//! # use scoped_callback::scope;
8//! /// Function for registering a callback with a `'static` lifetime.
9//! fn register(callback: Box<dyn FnMut(i32)>) -> Box<dyn FnMut(i32)> {
10//!   callback
11//! }
12//! /// Function for de-registering the handle returned by `register`,
13//! /// in this case the callback itself.
14//! fn deregister(_callback: Box<dyn FnMut(i32)>) {}
15//!
16//! /// Variable that can be borrowed from inside the callback closure
17//! let a = 42;
18//!
19//! /// After returning from the closure, `scope` guarantees that any callbacks
20//! /// that have not yet been de-registered are de-registered.
21//! scope(|scope| {
22//!
23//!   /// Register the given closure, which can borrow from the stack outside `scope`
24//!   /// using the `register` and `deregister` functions declared above.
25//!   /// The returned handle will cause a de-register when dropped.
26//!   let _registered = scope.register(
27//!     |_| {
28//!       let b = a * a;
29//!       println!("{}", b);
30//!     },
31//!     register,
32//!     deregister,
33//!   );
34//! });
35//! ```
36//! See [scope_async](fn.scope_async.html) and [scope_async_local](fn.scope_async_local.html)
37//! as well for versions that work with `async` scopes.
38//!
39//! # How is this safe?
40//! There are three important concepts in this implementation:
41//! * [register](struct.Scope.html#method.register) returns a [Registered](struct.Registered.html)
42//!   instance, which when [Drop](struct.Registered.html#impl-Drop)-ed causes the callback to be
43//!   de-registered using the provided function.
44//! * In case the [Registered](struct.Registered.html) instance is not
45//!   [Drop](struct.Registered.html#impl-Drop)-ed, for example by calling `std::mem::forget`
46//!   (which is *not* `unsafe`!)
47//!   the de-registering using the provided function will instead happen after leaving the closure
48//!   passed to [scope](fn.scope.html).
49//! * In case the given de-register function doesn't actually de-register the callback,
50//!   and for some reason the callback given to the [register](struct.Scope.html#method.register)
51//!   function is called after the closure passed to [scope](fn.scope.html), the call will cause a
52//!   `panic!`.
53//!
54//! # `no_std`
55//! This crate supports `no_std` by disabling its `std` feature.
56
57#![cfg_attr(not(feature = "std"), no_std)]
58
59#[cfg(not(feature = "std"))]
60extern crate alloc;
61
62#[cfg(not(feature = "std"))]
63use alloc::{boxed::Box, rc::Rc, vec::Vec};
64#[cfg(feature = "std")]
65use std::rc::Rc;
66
67unsafe fn transmute_lifetime<'a, A: 'static, R: 'static>(
68    value: Box<dyn FnMut(A) -> R + 'a>,
69) -> Box<dyn FnMut(A) -> R + 'static> {
70    core::mem::transmute(value)
71}
72
73#[cfg(feature = "async")]
74unsafe fn transmute_future_lifetime<'a, T: 'static>(
75    future: futures_util::future::LocalBoxFuture<'a, T>,
76) -> futures_util::future::LocalBoxFuture<'static, T> {
77    core::mem::transmute(future)
78}
79
80struct Deregister<'a>(core::cell::RefCell<Option<Box<dyn FnOnce() + 'a>>>);
81
82impl<'a> Deregister<'a> {
83    fn new(f: Box<dyn FnOnce() + 'a>) -> Self {
84        Self(core::cell::RefCell::new(Some(f)))
85    }
86
87    fn force(&self) {
88        if let Some(f) = self.0.borrow_mut().take() {
89            f();
90        }
91    }
92}
93
94impl<'a> Drop for Deregister<'a> {
95    fn drop(&mut self) {
96        self.force();
97    }
98}
99
100/// A handle returned by [Scope::register](struct.Scope.html#method.register).
101/// When this handle is dropped, the callback is de-registered.
102pub struct Registered<'env, 'scope> {
103    deregister: Rc<Deregister<'env>>,
104    marker: core::marker::PhantomData<&'scope ()>,
105}
106
107impl<'env, 'scope> Drop for Registered<'env, 'scope> {
108    fn drop(&mut self) {
109        self.deregister.force()
110    }
111}
112
113/// A `Scope` is used to register callbacks.
114/// See [Scope::register](struct.Scope.html#method.register).
115pub struct Scope<'env> {
116    callbacks: core::cell::RefCell<Vec<Rc<Deregister<'env>>>>,
117    marker: core::marker::PhantomData<&'env mut &'env ()>,
118}
119
120impl<'env> Scope<'env> {
121    fn new() -> Self {
122        Self {
123            callbacks: core::cell::RefCell::new(Vec::new()),
124            marker: core::marker::PhantomData,
125        }
126    }
127
128    /// Register the function `c` with local lifetime `'env` using the `register` and `deregister`
129    /// functions that handle only `'static` lifetime functions.
130    /// The returned `Registered` object will, when dropped, invoke the `deregister` function.
131    ///
132    /// If the `Registered` object is `std::mem::forget`-ed, `Scope::drop` will
133    /// perform the de-registration.
134    ///
135    /// *Note*: If the callback passed to the `register` function is invoked after `deregister`
136    /// has been invoked, the callback will `panic!`.
137    pub fn register<'scope, A: 'static, R: 'static, H: 'static>(
138        &'scope self,
139        c: impl (FnMut(A) -> R) + 'env,
140        register: impl FnOnce(Box<dyn FnMut(A) -> R>) -> H + 'env,
141        deregister: impl FnOnce(H) + 'env,
142    ) -> Registered<'env, 'scope> {
143        let c = unsafe { transmute_lifetime(Box::new(c)) };
144        let c = Rc::new(core::cell::RefCell::new(Some(c)));
145        let handle = {
146            let c = c.clone();
147            register(Box::new(move |arg| {
148                (c.as_ref()
149                    .borrow_mut()
150                    .as_mut()
151                    .expect("Callback used after scope is unsafe"))(arg)
152            }))
153        };
154        let deregister = Rc::new(Deregister::new(Box::new(move || {
155            deregister(handle);
156            c.as_ref().borrow_mut().take();
157        })));
158        self.callbacks.borrow_mut().push(deregister.clone());
159        Registered {
160            deregister,
161            marker: core::marker::PhantomData,
162        }
163    }
164
165    /// Wrap a scoped `Future` into one with a `'static` lifetime.
166    /// `Future::poll` will `panic!` if called after the scope has been dropped.
167    #[cfg(feature = "async")]
168    pub fn future<'scope>(
169        &'scope self,
170        future: futures_util::future::LocalBoxFuture<'env, ()>,
171    ) -> impl futures_util::future::Future<Output = ()> + 'static {
172        use std::{cell::RefCell, pin::Pin};
173
174        let future = unsafe { transmute_future_lifetime(future) };
175        let future = Rc::new(RefCell::new(Some(future)));
176        self.callbacks
177            .borrow_mut()
178            .push(Rc::new(Deregister::new(Box::new({
179                let future = future.clone();
180                move || {
181                    future.as_ref().borrow_mut().take();
182                }
183            }))));
184
185        use futures_util::{
186            future::{Future, LocalBoxFuture},
187            task::{Context, Poll},
188        };
189        struct StaticFuture(Rc<RefCell<Option<LocalBoxFuture<'static, ()>>>>);
190
191        impl Future for StaticFuture {
192            type Output = ();
193
194            fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
195                if let Some(future) = self.0.borrow_mut().as_mut() {
196                    Future::poll(future.as_mut(), cx)
197                } else {
198                    panic!("Future used after scope is unsafe")
199                }
200            }
201        }
202        StaticFuture(future)
203    }
204}
205
206impl<'env> Drop for Scope<'env> {
207    fn drop(&mut self) {
208        self.callbacks
209            .borrow()
210            .iter()
211            .for_each(|deregister| deregister.force());
212    }
213}
214
215/// Call `scope` to receive a `Scope` instance that can be used to register functions.
216/// See [Scope::register](struct.Scope.html#method.register).
217pub fn scope<'env, R>(f: impl FnOnce(&Scope<'env>) -> R) -> R {
218    f(&Scope::<'env>::new())
219}
220
221/// Same as [scope](fn.scope.html) but also allow `async` borrows.
222///
223/// The `Scope` instance passed to `f` can not outlive the call of this function.
224/// However, for async functions, this would be useless as the function returns a `Future`
225/// that is yet to complete, and may contain references to the given `Scope`.
226/// In order to remedy this, `scope_async` explicitly makes sure `Scope` lives throughout
227/// the lifetime of the future returned by `f`.
228#[cfg(feature = "async")]
229pub async fn scope_async<'env, R>(
230    f: impl for<'r> FnOnce(&'r Scope<'env>) -> futures_util::future::BoxFuture<'r, R>,
231) -> R {
232    f(&Scope::<'env>::new()).await
233}
234
235/// Same as [scope_async](fn.scope_async.html) but here `f` returns a `LocalBoxFuture` instead.
236#[cfg(feature = "async")]
237pub async fn scope_async_local<'env, R>(
238    f: impl for<'r> FnOnce(&'r Scope<'env>) -> futures_util::future::LocalBoxFuture<'r, R>,
239) -> R {
240    f(&Scope::<'env>::new()).await
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    fn register(callback: Box<dyn FnMut(i32)>) -> Box<dyn FnMut(i32)> {
248        callback
249    }
250
251    fn deregister(_callback: Box<dyn FnMut(i32)>) {}
252
253    #[test]
254    fn it_works() {
255        let a = 42;
256        scope(|scope| {
257            let registered = scope.register(
258                |_| {
259                    let _b = a * a;
260                },
261                register,
262                deregister,
263            );
264
265            core::mem::drop(registered);
266        });
267    }
268
269    #[test]
270    fn calling() {
271        let stored = Rc::new(core::cell::RefCell::new(None));
272        scope(|scope| {
273            let registered = scope.register(
274                |a| 2 * a,
275                |callback| {
276                    stored.as_ref().borrow_mut().replace(callback);
277                },
278                |_| {},
279            );
280
281            assert_eq!((stored.as_ref().borrow_mut().as_mut().unwrap())(42), 2 * 42);
282
283            core::mem::drop(registered);
284        });
285    }
286
287    #[test]
288    fn drop_registered_causes_deregister() {
289        let dropped = Rc::new(core::cell::Cell::new(false));
290        scope(|scope| {
291            let registered = scope.register(|_| {}, register, {
292                let dropped = dropped.clone();
293                move |_| dropped.as_ref().set(true)
294            });
295
296            core::mem::drop(registered);
297            assert!(dropped.as_ref().get());
298        });
299    }
300
301    #[test]
302    fn leaving_scope_causes_deregister() {
303        let dropped = Rc::new(core::cell::Cell::new(false));
304        scope(|scope| {
305            let registered = scope.register(|_| {}, register, {
306                let dropped = dropped.clone();
307                move |_| dropped.as_ref().set(true)
308            });
309
310            core::mem::forget(registered);
311            assert!(!dropped.as_ref().get());
312        });
313        assert!(dropped.as_ref().get());
314    }
315
316    #[test]
317    /// Note: catch_unwind not available with `no_std`,
318    /// See https://github.com/rust-lang/rfcs/issues/2810
319    #[cfg(feature = "std")]
320    fn calling_static_callback_after_drop_panics() {
321        let res = std::panic::catch_unwind(|| {
322            let stored = Rc::new(core::cell::RefCell::new(None));
323            scope(|scope| {
324                let registered = scope.register(
325                    |_| {},
326                    |callback| {
327                        stored.as_ref().borrow_mut().replace(callback);
328                    },
329                    |_| {},
330                );
331
332                core::mem::drop(registered);
333                (stored.as_ref().borrow_mut().as_mut().unwrap())(42);
334            });
335        });
336        assert!(res.is_err());
337    }
338
339    #[test]
340    /// Note: catch_unwind not available with `no_std`,
341    /// See https://github.com/rust-lang/rfcs/issues/2810
342    #[cfg(feature = "std")]
343    fn calling_static_callback_after_scope_panics() {
344        let res = std::panic::catch_unwind(|| {
345            let stored = Rc::new(core::cell::RefCell::new(None));
346            scope(|scope| {
347                let registered = scope.register(
348                    |_| {},
349                    |callback| {
350                        stored.as_ref().borrow_mut().replace(callback);
351                    },
352                    |_| {},
353                );
354
355                core::mem::forget(registered);
356            });
357            (stored.as_ref().borrow_mut().as_mut().unwrap())(42);
358        });
359        assert!(res.is_err());
360    }
361
362    #[test]
363    /// Note: catch_unwind not available with `no_std`,
364    /// See https://github.com/rust-lang/rfcs/issues/2810
365    #[cfg(feature = "std")]
366    fn panic_in_scoped_is_safe() {
367        let stored = std::sync::Mutex::new(None);
368        let res = std::panic::catch_unwind(|| {
369            scope(|scope| {
370                let registered = scope.register(
371                    |_| {},
372                    |callback| {
373                        stored.lock().unwrap().replace(callback);
374                    },
375                    |_| {},
376                );
377
378                core::mem::forget(registered);
379                panic!()
380            });
381        });
382        assert!(res.is_err());
383        let res = std::panic::catch_unwind(|| {
384            (stored.lock().unwrap().as_mut().take().unwrap())(42);
385        });
386        assert!(res.is_err());
387    }
388}