Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 104 additions & 31 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#![cfg_attr(not(test), no_std)]
#![warn(missing_docs)]

use core::fmt::Debug;
use core::fmt::Display;
use core::future::Future;
use core::marker::PhantomData;
use core::mem;
Expand Down Expand Up @@ -147,34 +149,7 @@ impl<'a, T, const STACK_SIZE: usize> StackFuture<'a, T, { STACK_SIZE }> {
#[allow(clippy::let_unit_value)]
let _ = AssertFits::<F, STACK_SIZE>::ASSERT;

// Since we have the static assert above, we know this will not fail. This means we could
// use `unwrap_unchecked` here. The extra `unsafe` code probably isn't worth it since very
// likely the compiler will be able to make that optimization for us, this comment is here
// as a reminder that we can change it if for some reason this ends up being a performance
// issue in the future.
Self::try_from(future).unwrap_or_else(|f| {
match (Self::has_alignment_for_val(&f), Self::has_space_for_val(&f)) {
(false, false) => panic!(
"cannot create StackFuture, required size is {}, available space is {}; required alignment is {} but maximum alignment is {}",
mem::size_of_val(&f),
STACK_SIZE,
mem::align_of::<F>(),
mem::align_of::<Self>()
),
(false, true) => panic!(
"cannot create StackFuture, required alignment is {} but maximum alignment is {}",
mem::align_of::<F>(),
mem::align_of::<Self>()
),
(true, false) => panic!(
"cannot create StackFuture, required size is {}, available space is {}",
mem::size_of_val(&f),
STACK_SIZE
),
// If we have space and alignment, then `try_from` would have succeeded
(true, true) => unreachable!(),
}
})
Self::try_from(future).unwrap()
}

/// Attempts to create a `StackFuture` from an existing future
Expand All @@ -185,7 +160,7 @@ impl<'a, T, const STACK_SIZE: usize> StackFuture<'a, T, { STACK_SIZE }> {
/// Panics
///
/// If we cannot satisfy the alignment requirements for `F`, this function will panic.
pub fn try_from<F>(future: F) -> Result<Self, F>
pub fn try_from<F>(future: F) -> Result<Self, IntoStackFutureError<F>>
where
F: Future<Output = T> + Send + 'a, // the bounds here should match those in the _phantom field
{
Expand All @@ -212,7 +187,7 @@ impl<'a, T, const STACK_SIZE: usize> StackFuture<'a, T, { STACK_SIZE }> {

Ok(result)
} else {
Err(future)
Err(IntoStackFutureError::new::<Self>(future))
}
}

Expand All @@ -231,7 +206,7 @@ impl<'a, T, const STACK_SIZE: usize> StackFuture<'a, T, { STACK_SIZE }> {
where
F: Future<Output = T> + Send + 'a, // the bounds here should match those in the _phantom field
{
Self::try_from(future).unwrap_or_else(|future| Self::from(Box::pin(future)))
Self::try_from(future).unwrap_or_else(|err| Self::from(Box::pin(err.into_inner())))
}

/// A wrapper around the inner future's poll function, which we store in the poll_fn field
Expand Down Expand Up @@ -338,5 +313,103 @@ impl<F, const STACK_SIZE: usize> AssertFits<F, STACK_SIZE> {
};
}

/// Captures information about why a future could not be converted into a [`StackFuture`]
///
/// It also contains the original future so that callers can still run the future in error
/// recovery paths, such as by boxing the future instead of wrapping it in [`StackFuture`].
pub struct IntoStackFutureError<F> {
/// The size of the StackFuture we tried to convert the future into
maximum_size: usize,
/// The StackFuture's alignment
maximum_alignment: usize,
/// The future that was attempted to be wrapped
future: F,
}

impl<F> IntoStackFutureError<F> {
fn new<Target>(future: F) -> Self {
Self {
maximum_size: mem::size_of::<Target>(),
maximum_alignment: mem::align_of::<Target>(),
future,
}
}

/// Returns true if the target [`StackFuture`] was too small to hold the given future.
pub fn insufficient_space(&self) -> bool {
self.maximum_size < mem::size_of_val(&self.future)
}

/// Returns true if the target [`StackFuture`]'s alignment was too small to accommodate the given future.
pub fn alignment_too_small(&self) -> bool {
self.maximum_alignment < mem::align_of_val(&self.future)
}

/// Returns the alignment of the wrapped future.
pub fn required_alignment(&self) -> usize {
mem::align_of_val(&self.future)
}

/// Returns the size of the wrapped future.
pub fn required_space(&self) -> usize {
mem::size_of_val(&self.future)
}

/// Returns the alignment of the target [`StackFuture`], which is also the maximum alignment
/// that can be wrapped.
pub const fn available_alignment(&self) -> usize {
self.maximum_alignment
}

/// Returns the amount of space that was available in the target [`StackFuture`].
pub const fn available_space(&self) -> usize {
self.maximum_size
}

/// Returns the underlying future that caused this error
///
/// Can be used to try again, either by directly awaiting the future, wrapping it in a `Box`,
/// or some other method.
fn into_inner(self) -> F {
self.future
}
}

impl<F> Display for IntoStackFutureError<F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match (self.alignment_too_small(), self.insufficient_space()) {
(true, true) => write!(f,
"cannot create StackFuture, required size is {}, available space is {}; required alignment is {} but maximum alignment is {}",
self.required_space(),
self.available_space(),
self.required_alignment(),
self.available_alignment()
),
(true, false) => write!(f,
"cannot create StackFuture, required alignment is {} but maximum alignment is {}",
self.required_alignment(),
self.available_alignment()
),
(false, true) => write!(f,
"cannot create StackFuture, required size is {}, available space is {}",
self.required_space(),
self.available_space()
),
// If we have space and alignment, then `try_from` would have succeeded
(false, false) => unreachable!(),
}
}
}

impl<F> Debug for IntoStackFutureError<F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("IntoStackFutureError")
.field("maximum_size", &self.maximum_size)
.field("maximum_alignment", &self.maximum_alignment)
.field("future", &core::any::type_name::<F>())
.finish()
}
}

#[cfg(test)]
mod tests;
14 changes: 11 additions & 3 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ fn test_size_failure() {
buf[0]
};

assert!(StackFuture::<_, 4>::try_from(f).is_err());
match StackFuture::<_, 4>::try_from(f) {
Ok(_) => panic!("conversion to StackFuture should not have succeeded"),
Err(e) => assert!(e.insufficient_space()),
}
}

#[test]
Expand Down Expand Up @@ -127,7 +130,10 @@ fn test_alignment_failure() {
Poll::Pending
}
}
assert!(StackFuture::<'_, _, 1016>::try_from(BigAlignment(42)).is_err());
match StackFuture::<'_, _, 1016>::try_from(BigAlignment(42)) {
Ok(_) => panic!("conversion to StackFuture should not have succeeded"),
Err(e) => assert!(e.alignment_too_small()),
}
}

#[cfg(feature = "alloc")]
Expand Down Expand Up @@ -189,7 +195,9 @@ fn try_from() {

match StackFuture::<_, 10>::try_from(big_future) {
Ok(_) => panic!("try_from should not have succeeded"),
Err(big_future) => assert!(StackFuture::<_, 1500>::try_from(big_future).is_ok()),
Err(big_future) => {
assert!(StackFuture::<_, 1500>::try_from(big_future.into_inner()).is_ok())
}
};
}

Expand Down