scoped_futures/
lib.rs

1#![no_std]
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6#[cfg(feature = "alloc")]
7use alloc::boxed::Box;
8use core::{future::Future, marker::PhantomData, pin::Pin};
9
10/// A [`Future`] super-trait with an implied upper bound on the provided lifetime.
11/// This is especially useful for callbacks that use higher-ranked lifetimes in their return type,
12/// where it can prevent `'static` bounds from being placed on a returned [`Future`].
13///
14/// # Example
15/// ```
16/// # fn test() {
17/// use core::pin::Pin;
18/// use scoped_futures::{ScopedBoxFuture, ScopedFutureExt};
19///
20/// pub struct Db {
21///     count: u8,
22/// }
23///
24/// impl Db {
25///     async fn transaction<'a, F, T, E>(&mut self, callback: F) -> Result<T, E>
26///     where
27///         // ScopedBoxFuture imposes a lifetime bound on 'b which prevents the hrtb below needing
28///         // to be satisfied for all lifetimes (including 'static) and instead only lifetimes
29///         // which live at most as long as 'a
30///         F: for<'b /* where 'a: 'b */> FnOnce(&'b mut Self) -> ScopedBoxFuture<'a, 'b, Result<T, E>> + Send + 'a,
31///         T: 'a,
32///         E: 'a,
33///     {
34///         callback(self).await
35///     }
36/// }
37///
38/// pub async fn test_transaction<'a, 'b>(
39///     db: &mut Db,
40///     ok: &'a str,
41///     err: &'b str,
42///     is_ok: bool,
43/// ) -> Result<&'a str, &'b str> {
44///     // note the lack of `move` or any cloning in front of the closure
45///     db.transaction(|db| async move {
46///         db.count += 1;
47///         if is_ok {
48///             Ok(ok)
49///         } else {
50///             Err(err)
51///         }
52///     }.scope_boxed()).await?;
53///
54///     // note that `async` can be used instead of `async move` since the callback param is unused
55///     db.transaction(|_| async {
56///         if is_ok {
57///             Ok(ok)
58///         } else {
59///             Err(err)
60///         }
61///     }.scope_boxed()).await
62/// }
63///
64/// futures::executor::block_on(async {
65///     let mut db = Db { count: 0 };
66///     let ok = String::from("ok");
67///     let err = String::from("err");
68///     let result = test_transaction(&mut db, &ok, &err, true).await;
69///     assert_eq!(ok, result.unwrap());
70///     assert_eq!(1, db.count);
71/// });
72/// # } #[cfg(feature = "alloc")] test();
73/// ```
74pub trait ScopedFuture<'upper_bound, 'subject, Bound = ImpliedLifetimeBound<'upper_bound, 'subject>>: Future
75where
76    Bound: sealed::Sealed,
77{
78}
79
80/// A wrapper type which imposes an upper bound on a lifetime.
81pub type ImpliedLifetimeBound<'upper_bound, 'subject> = PhantomData<&'subject &'upper_bound ()>;
82
83impl<'upper_bound: 'subject, 'subject, Fut: Future + 'subject> ScopedFuture<'upper_bound, 'subject> for Fut {}
84
85mod sealed {
86    pub trait Sealed {}
87    impl<'upper_bound, 'a> Sealed for super::ImpliedLifetimeBound<'upper_bound, 'a> {}
88}
89
90/// A boxed future whose lifetime is upper bounded.
91#[cfg(feature = "alloc")]
92pub type ScopedBoxFuture<'upper_bound, 'subject, T> = Pin<Box<dyn ScopedFuture<'upper_bound, 'subject, Output = T> + Send + 'subject>>;
93
94/// A non-[`Send`] boxed future whose lifetime is upper bounded.
95#[cfg(feature = "alloc")]
96pub type ScopedLocalBoxFuture<'upper_bound, 'subject, T> = Pin<Box<dyn ScopedFuture<'upper_bound, 'subject, Output = T> + 'subject>>;
97
98pin_project_lite::pin_project! {
99    /// A [`Future`] wrapper type that imposes an upper bound on its lifetime's duration.
100    #[derive(Clone, Debug)]
101    pub struct ScopedFutureWrapper<'upper_bound, 'subject, Fut> {
102        #[pin]
103        future: Fut,
104        scope: ImpliedLifetimeBound<'upper_bound, 'subject>,
105    }
106}
107
108/// An extension trait for [`Future`] that provides methods for encoding lifetime upper bound information.
109pub trait ScopedFutureExt: Sized {
110    /// Encodes the lifetimes of this [`Future`]'s captures.
111    fn scoped<'upper_bound, 'subject>(self) -> ScopedFutureWrapper<'upper_bound, 'subject, Self>;
112
113    /// Boxes this [`Future`] and encodes the lifetimes of its captures.
114    #[cfg(feature = "alloc")]
115    fn scope_boxed<'upper_bound, 'subject>(self) -> ScopedBoxFuture<'upper_bound, 'subject, <Self as Future>::Output>
116    where
117        Self: Send + Future + 'subject;
118
119    /// Boxes this [`Future`] and encodes the lifetimes of its captures.
120    #[cfg(feature = "alloc")]
121    fn scope_boxed_local<'upper_bound, 'subject>(self) -> ScopedLocalBoxFuture<'upper_bound, 'subject, <Self as Future>::Output>
122    where
123        Self: Future + 'subject;
124}
125
126impl<'upper_bound, 'subject, Fut: Future> Future for ScopedFutureWrapper<'upper_bound, 'subject, Fut> {
127    type Output = Fut::Output;
128    fn poll(self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> core::task::Poll<Self::Output> {
129        self.project().future.poll(cx)
130    }
131}
132
133impl<Fut: Future> ScopedFutureExt for Fut {
134    fn scoped<'upper_bound, 'subject>(self) -> ScopedFutureWrapper<'upper_bound, 'subject, Self> {
135        ScopedFutureWrapper { future: self, scope: PhantomData }
136    }
137
138    #[cfg(feature = "alloc")]
139    fn scope_boxed<'upper_bound, 'subject>(self) -> ScopedBoxFuture<'upper_bound, 'subject, <Self as Future>::Output>
140    where
141        Self: Send + Future + 'subject,
142    {
143        Box::pin(self)
144    }
145
146    #[cfg(feature = "alloc")]
147    fn scope_boxed_local<'upper_bound, 'subject>(self) -> ScopedLocalBoxFuture<'upper_bound, 'subject, <Self as Future>::Output>
148    where
149        Self: Future + 'subject,
150    {
151        Box::pin(self)
152    }
153}
154
155#[cfg(feature = "alloc")]
156const _: () = {
157    impl<'upper_bound, 'subject, T, Fut: Future<Output = T> + Send + 'subject> From<Pin<Box<Fut>>> for ScopedBoxFuture<'upper_bound, 'subject, T> {
158        fn from(future: Pin<Box<Fut>>) -> Self {
159            future
160        }
161    }
162
163    impl<'upper_bound, 'subject, T, Fut: Future<Output = T> + 'subject> From<Pin<Box<Fut>>> for ScopedLocalBoxFuture<'upper_bound, 'subject, T> {
164        fn from(future: Pin<Box<Fut>>) -> Self {
165            future
166        }
167    }
168
169    impl<'upper_bound, 'subject, T, Fut: Future<Output = T> + Send + 'subject> From<Box<Fut>> for ScopedBoxFuture<'upper_bound, 'subject, T> {
170        fn from(future: Box<Fut>) -> Self {
171            Box::into_pin(future)
172        }
173    }
174
175    impl<'upper_bound, 'subject, T, Fut: Future<Output = T> + 'subject> From<Box<Fut>> for ScopedLocalBoxFuture<'upper_bound, 'subject, T> {
176        fn from(future: Box<Fut>) -> Self {
177            Box::into_pin(future)
178        }
179    }
180
181    impl<'upper_bound, 'subject, T: 'subject> From<Pin<Box<dyn Future<Output = T> + Send + 'subject>>> for ScopedBoxFuture<'upper_bound, 'subject, T> {
182        fn from(future: Pin<Box<dyn Future<Output = T> + Send + 'subject>>) -> Self {
183            Box::pin(future)
184        }
185    }
186
187    impl<'upper_bound, 'subject, T: 'subject> From<Pin<Box<dyn Future<Output = T> + 'subject>>> for ScopedLocalBoxFuture<'upper_bound, 'subject, T> {
188        fn from(future: Pin<Box<dyn Future<Output = T> + 'subject>>) -> Self {
189            Box::pin(future)
190        }
191    }
192
193    impl<'upper_bound, 'subject, T: 'subject> From<Box<dyn Future<Output = T> + Send + 'subject>> for ScopedBoxFuture<'upper_bound, 'subject, T> {
194        fn from(future: Box<dyn Future<Output = T> + Send + 'subject>) -> Self {
195            Box::into_pin(future).into()
196        }
197    }
198
199    impl<'upper_bound, 'subject, T: 'subject> From<Box<dyn Future<Output = T> + 'subject>> for ScopedLocalBoxFuture<'upper_bound, 'subject, T> {
200        fn from(future: Box<dyn Future<Output = T> + 'subject>) -> Self {
201            Box::into_pin(future).into()
202        }
203    }
204
205    impl<'upper_bound, 'subject, T: 'subject> From<ScopedBoxFuture<'upper_bound, 'subject, T>> for Pin<Box<dyn Future<Output = T> + Send + 'subject>> {
206        fn from(future: ScopedBoxFuture<'upper_bound, 'subject, T>) -> Self {
207            Box::pin(future)
208        }
209    }
210
211    impl<'upper_bound, 'subject, T: 'subject> From<ScopedLocalBoxFuture<'upper_bound, 'subject, T>> for Pin<Box<dyn Future<Output = T> + 'subject>> {
212        fn from(future: ScopedLocalBoxFuture<'upper_bound, 'subject, T>) -> Self {
213            Box::pin(future)
214        }
215    }
216};