stackfuture/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! This crate defines a `StackFuture` wrapper around futures that stores the wrapped
5//! future in space provided by the caller. This can be used to emulate dyn async traits
6//! without requiring heap allocation.
7//!
8//! For more details, see the documentation on the [`StackFuture`] struct.
9
10// std is needed to run tests, but otherwise we don't need it.
11#![cfg_attr(not(test), no_std)]
12#![warn(missing_docs)]
13
14use core::fmt::Debug;
15use core::fmt::Display;
16use core::future::Future;
17use core::marker::PhantomData;
18use core::mem;
19use core::mem::MaybeUninit;
20use core::pin::Pin;
21use core::ptr;
22use core::task::Context;
23use core::task::Poll;
24
25#[cfg(feature = "alloc")]
26extern crate alloc;
27
28#[cfg(feature = "alloc")]
29use alloc::boxed::Box;
30
31/// A wrapper that stores a future in space allocated by the container
32///
33/// Often this space comes from the calling function's stack, but it could just
34/// as well come from some other allocation.
35///
36/// A `StackFuture` can be used to emulate async functions in dyn Trait objects.
37/// For example:
38///
39/// ```
40/// # use stackfuture::*;
41/// trait PseudoAsyncTrait {
42///     fn do_something(&self) -> StackFuture<'_, (), { 512 }>;
43/// }
44///
45/// impl PseudoAsyncTrait for i32 {
46///     fn do_something(&self) -> StackFuture<'_, (), { 512 }> {
47///         StackFuture::from(async {
48///             // function body goes here
49///         })
50///     }
51/// }
52///
53/// async fn use_dyn_async_trait(x: &dyn PseudoAsyncTrait) {
54///     x.do_something().await;
55/// }
56///
57/// async fn call_with_dyn_async_trait() {
58///     use_dyn_async_trait(&42).await;
59/// }
60/// ```
61///
62/// This example defines `PseudoAsyncTrait` with a single method `do_something`.
63/// The `do_something` method can be called as if it were declared as
64/// `async fn do_something(&self)`. To implement `do_something`, the easiest thing
65/// to do is to wrap the body of the function in `StackFuture::from(async { ... })`,
66/// which creates an anonymous future for the body and stores it in a `StackFuture`.
67///
68/// Because `StackFuture` does not know the size of the future it wraps, the maximum
69/// size of the future must be specified in the `STACK_SIZE` parameter. In the example
70/// here, we've used a stack size of 512, which is probably much larger than necessary
71/// but would accommodate many futures besides the simple one we've shown here.
72///
73/// `StackFuture` ensures when wrapping a future that enough space is available, and
74/// it also respects any alignment requirements for the wrapped future. Note that the
75/// wrapped future's alignment must be less than or equal to that of the overall
76/// `StackFuture` struct.
77#[repr(C)] // Ensures the data first does not have any padding before it in the struct
78pub struct StackFuture<'a, T, const STACK_SIZE: usize> {
79    /// An array of bytes that is used to store the wrapped future.
80    data: [MaybeUninit<u8>; STACK_SIZE],
81    /// Since the type of `StackFuture` does not know the underlying future that it is wrapping,
82    /// we keep a manual vtable that serves pointers to Poll::poll and Drop::drop. These are
83    /// generated and filled in by `StackFuture::from`.
84    ///
85    /// This field stores a pointer to the poll function wrapper.
86    poll_fn: fn(this: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T>,
87    /// Stores a pointer to the drop function wrapper
88    ///
89    /// See the documentation on `poll_fn` for more details.
90    drop_fn: fn(this: &mut Self),
91    /// StackFuture can be used similarly to a `dyn Future`. We keep a PhantomData
92    /// here so the type system knows this.
93    _phantom: PhantomData<dyn Future<Output = T> + Send + 'a>,
94}
95
96impl<'a, T, const STACK_SIZE: usize> StackFuture<'a, T, { STACK_SIZE }> {
97    /// Creates a `StackFuture` from an existing future
98    ///
99    /// See the documentation on [`StackFuture`] for examples of how to use this.
100    ///
101    /// The size and alignment requirements are statically checked, so it is a compiler error
102    /// to use this with a future that does not fit within the [`StackFuture`]'s size and
103    /// alignment requirements.
104    ///
105    /// The following example illustrates a compile error for a future that is too large.
106    /// ```compile_fail
107    /// # use stackfuture::StackFuture;
108    /// // Fails because the future contains a large array and is therefore too big to fit in
109    /// // a 16-byte `StackFuture`.
110    /// let f = StackFuture::<_, { 16 }>::from(async {
111    ///     let x = [0u8; 4096];
112    ///     async {}.await;
113    ///     println!("{}", x.len());
114    /// });
115    /// # #[cfg(miri)] break rust; // FIXME: miri doesn't detect this breakage for some reason...
116    /// ```
117    ///
118    /// The example below illustrates a compiler error for a future whose alignment is too large.
119    /// ```compile_fail
120    /// # use stackfuture::StackFuture;
121    ///
122    /// #[derive(Debug)]
123    /// #[repr(align(256))]
124    /// struct BigAlignment(usize);
125    ///
126    /// // Fails because the future contains a large array and is therefore too big to fit in
127    /// // a 16-byte `StackFuture`.
128    /// let f = StackFuture::<_, { 16 }>::from(async {
129    ///     let x = BigAlignment(42);
130    ///     async {}.await;
131    ///     println!("{x:?}");
132    /// });
133    /// # #[cfg(miri)] break rust; // FIXME: miri doesn't detect this breakage for some reason...
134    /// ```
135    pub fn from<F>(future: F) -> Self
136    where
137        F: Future<Output = T> + Send + 'a, // the bounds here should match those in the _phantom field
138    {
139        // Ideally we would provide this as:
140        //
141        //     impl<'a, F, const STACK_SIZE: usize> From<F> for  StackFuture<'a, F::Output, { STACK_SIZE }>
142        //     where
143        //         F: Future + Send + 'a
144        //
145        // However, libcore provides a blanket `impl<T> From<T> for T`, and since `StackFuture: Future`,
146        // both impls end up being applicable to do `From<StackFuture> for StackFuture`.
147
148        // Statically assert that `F` meets all the size and alignment requirements
149        #[allow(clippy::let_unit_value)]
150        let _ = AssertFits::<F, STACK_SIZE>::ASSERT;
151
152        Self::try_from(future).unwrap()
153    }
154
155    /// Attempts to create a `StackFuture` from an existing future
156    ///
157    /// If the `StackFuture` is not large enough to hold `future`, this function returns an
158    /// `Err` with the argument `future` returned to you.
159    ///
160    /// Panics
161    ///
162    /// If we cannot satisfy the alignment requirements for `F`, this function will panic.
163    pub fn try_from<F>(future: F) -> Result<Self, IntoStackFutureError<F>>
164    where
165        F: Future<Output = T> + Send + 'a, // the bounds here should match those in the _phantom field
166    {
167        if Self::has_space_for_val(&future) && Self::has_alignment_for_val(&future) {
168            let mut result = StackFuture {
169                data: [MaybeUninit::uninit(); STACK_SIZE],
170                poll_fn: Self::poll_inner::<F>,
171                drop_fn: Self::drop_inner::<F>,
172                _phantom: PhantomData,
173            };
174
175            // Ensure result.data is at the beginning of the struct so we don't need to do
176            // alignment adjustments.
177            assert_eq!(result.data.as_ptr() as usize, &result as *const _ as usize);
178
179            // SAFETY: result.as_mut_ptr returns a pointer into result.data, which is an
180            // uninitialized array of bytes. result.as_mut_ptr ensures the returned pointer
181            // is correctly aligned, and the if expression we are in ensures the buffer is
182            // large enough.
183            //
184            // Because `future` is bound by `'a` and `StackFuture` is also bound by `'a`,
185            // we can be sure anything that `future` closes over will also outlive `result`.
186            unsafe { result.as_mut_ptr::<F>().write(future) };
187
188            Ok(result)
189        } else {
190            Err(IntoStackFutureError::new::<Self>(future))
191        }
192    }
193
194    /// Creates a StackFuture from the given future, boxing if necessary
195    ///
196    /// This version will succeed even if the future is larger than `STACK_SIZE`. If the future
197    /// is too large, `from_or_box` will allocate a `Box` on the heap and store the resulting
198    /// boxed future in the `StackFuture`.
199    ///
200    /// The same thing also happens if the wrapped future's alignment is larger than StackFuture's
201    /// alignment.
202    ///
203    /// This function requires the "alloc" crate feature.
204    #[cfg(feature = "alloc")]
205    pub fn from_or_box<F>(future: F) -> Self
206    where
207        F: Future<Output = T> + Send + 'a, // the bounds here should match those in the _phantom field
208    {
209        Self::try_from(future).unwrap_or_else(|err| Self::from(Box::pin(err.into_inner())))
210    }
211
212    /// A wrapper around the inner future's poll function, which we store in the poll_fn field
213    /// of this struct.
214    fn poll_inner<F: Future>(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
215        self.as_pin_mut_ref::<F>().poll(cx)
216    }
217
218    /// A wrapper around the inner future's drop function, which we store in the drop_fn field
219    /// of this struct.
220    fn drop_inner<F>(&mut self) {
221        // SAFETY: *this.as_mut_ptr() was previously written as type F
222        unsafe { ptr::drop_in_place(self.as_mut_ptr::<F>()) }
223    }
224
225    /// Returns a pointer into self.data that meets the alignment requirements for type `F`
226    ///
227    /// Before writing to the returned pointer, the caller must ensure that self.data is large
228    /// enough to hold F and any required padding.
229    fn as_mut_ptr<F>(&mut self) -> *mut F {
230        assert!(Self::has_space_for::<F>());
231        // SAFETY: Self is laid out so that the space for the future comes at offset 0.
232        // This is checked by an assertion in Self::from. Thus it's safe to cast a pointer
233        // to Self into a pointer to the wrapped future.
234        unsafe { mem::transmute(self) }
235    }
236
237    /// Returns a pinned mutable reference to a type F stored in self.data
238    fn as_pin_mut_ref<F>(self: Pin<&mut Self>) -> Pin<&mut F> {
239        // SAFETY: `StackFuture` is only created by `StackFuture::from`, which
240        // writes an `F` to `self.as_mut_ptr(), so it's okay to cast the `*mut F`
241        // to an `&mut F` with the same lifetime as `self`.
242        //
243        // For pinning, since self is already pinned, we know the wrapped future
244        // is also pinned.
245        //
246        // This function is only doing pointer arithmetic and casts, so we aren't moving
247        // any pinned data.
248        unsafe { self.map_unchecked_mut(|this| &mut *this.as_mut_ptr()) }
249    }
250
251    /// Computes how much space is required to store a value of type `F`
252    const fn required_space<F>() -> usize {
253        mem::size_of::<F>()
254    }
255
256    /// Determines whether this `StackFuture` can hold a value of type `F`
257    pub const fn has_space_for<F>() -> bool {
258        Self::required_space::<F>() <= STACK_SIZE
259    }
260
261    /// Determines whether this `StackFuture` can hold the referenced value
262    pub const fn has_space_for_val<F>(_: &F) -> bool {
263        Self::has_space_for::<F>()
264    }
265
266    /// Determines whether this `StackFuture`'s alignment is compatible with the
267    /// type `F`.
268    pub const fn has_alignment_for<F>() -> bool {
269        mem::align_of::<F>() <= mem::align_of::<Self>()
270    }
271
272    /// Determines whether this `StackFuture`'s alignment is compatible with the
273    /// referenced value.
274    pub const fn has_alignment_for_val<F>(_: &F) -> bool {
275        Self::has_alignment_for::<F>()
276    }
277}
278
279impl<'a, T, const STACK_SIZE: usize> Future for StackFuture<'a, T, { STACK_SIZE }> {
280    type Output = T;
281
282    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
283        // SAFETY: This is doing pin projection. We unpin self so we can
284        // access self.poll_fn, and then re-pin self to pass it into poll_in.
285        // The part of the struct that needs to be pinned is data, since it
286        // contains a potentially self-referential future object, but since we
287        // do not touch that while self is unpinned and we do not move self
288        // while unpinned we are okay.
289        unsafe {
290            let this = self.get_unchecked_mut();
291            (this.poll_fn)(Pin::new_unchecked(this), cx)
292        }
293    }
294}
295
296impl<'a, T, const STACK_SIZE: usize> Drop for StackFuture<'a, T, { STACK_SIZE }> {
297    fn drop(&mut self) {
298        (self.drop_fn)(self);
299    }
300}
301
302struct AssertFits<F, const STACK_SIZE: usize>(PhantomData<F>);
303
304impl<F, const STACK_SIZE: usize> AssertFits<F, STACK_SIZE> {
305    const ASSERT: () = {
306        if !StackFuture::<F, STACK_SIZE>::has_space_for::<F>() {
307            panic!("F is too large");
308        }
309
310        if !StackFuture::<F, STACK_SIZE>::has_alignment_for::<F>() {
311            panic!("F has incompatible alignment");
312        }
313    };
314}
315
316/// Captures information about why a future could not be converted into a [`StackFuture`]
317///
318/// It also contains the original future so that callers can still run the future in error
319/// recovery paths, such as by boxing the future instead of wrapping it in [`StackFuture`].
320pub struct IntoStackFutureError<F> {
321    /// The size of the StackFuture we tried to convert the future into
322    maximum_size: usize,
323    /// The StackFuture's alignment
324    maximum_alignment: usize,
325    /// The future that was attempted to be wrapped
326    future: F,
327}
328
329impl<F> IntoStackFutureError<F> {
330    fn new<Target>(future: F) -> Self {
331        Self {
332            maximum_size: mem::size_of::<Target>(),
333            maximum_alignment: mem::align_of::<Target>(),
334            future,
335        }
336    }
337
338    /// Returns true if the target [`StackFuture`] was too small to hold the given future.
339    pub fn insufficient_space(&self) -> bool {
340        self.maximum_size < mem::size_of_val(&self.future)
341    }
342
343    /// Returns true if the target [`StackFuture`]'s alignment was too small to accommodate the given future.
344    pub fn alignment_too_small(&self) -> bool {
345        self.maximum_alignment < mem::align_of_val(&self.future)
346    }
347
348    /// Returns the alignment of the wrapped future.
349    pub fn required_alignment(&self) -> usize {
350        mem::align_of_val(&self.future)
351    }
352
353    /// Returns the size of the wrapped future.
354    pub fn required_space(&self) -> usize {
355        mem::size_of_val(&self.future)
356    }
357
358    /// Returns the alignment of the target [`StackFuture`], which is also the maximum alignment
359    /// that can be wrapped.
360    pub const fn available_alignment(&self) -> usize {
361        self.maximum_alignment
362    }
363
364    /// Returns the amount of space that was available in the target [`StackFuture`].
365    pub const fn available_space(&self) -> usize {
366        self.maximum_size
367    }
368
369    /// Returns the underlying future that caused this error
370    ///
371    /// Can be used to try again, either by directly awaiting the future, wrapping it in a `Box`,
372    /// or some other method.
373    fn into_inner(self) -> F {
374        self.future
375    }
376}
377
378impl<F> Display for IntoStackFutureError<F> {
379    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
380        match (self.alignment_too_small(), self.insufficient_space()) {
381            (true, true) => write!(f,
382                "cannot create StackFuture, required size is {}, available space is {}; required alignment is {} but maximum alignment is {}",
383                self.required_space(),
384                self.available_space(),
385                self.required_alignment(),
386                self.available_alignment()
387            ),
388            (true, false) => write!(f,
389                "cannot create StackFuture, required alignment is {} but maximum alignment is {}",
390                self.required_alignment(),
391                self.available_alignment()
392            ),
393            (false, true) => write!(f,
394                "cannot create StackFuture, required size is {}, available space is {}",
395                self.required_space(),
396                self.available_space()
397            ),
398            // If we have space and alignment, then `try_from` would have succeeded
399            (false, false) => unreachable!(),
400        }
401    }
402}
403
404impl<F> Debug for IntoStackFutureError<F> {
405    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
406        f.debug_struct("IntoStackFutureError")
407            .field("maximum_size", &self.maximum_size)
408            .field("maximum_alignment", &self.maximum_alignment)
409            .field("future", &core::any::type_name::<F>())
410            .finish()
411    }
412}
413
414#[cfg(test)]
415mod tests;