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