Skip to content

Commit 3410144

Browse files
committed
Add IntoStackFutureError to provide more detail about conversion failures
1 parent 9a27962 commit 3410144

File tree

2 files changed

+120
-34
lines changed

2 files changed

+120
-34
lines changed

src/lib.rs

Lines changed: 108 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
#![cfg_attr(not(test), no_std)]
1212
#![warn(missing_docs)]
1313

14+
use core::fmt::Debug;
15+
use core::fmt::Display;
1416
use core::future::Future;
17+
use core::future::IntoFuture;
1518
use core::marker::PhantomData;
1619
use core::mem;
1720
use core::mem::MaybeUninit;
@@ -147,34 +150,7 @@ impl<'a, T, const STACK_SIZE: usize> StackFuture<'a, T, { STACK_SIZE }> {
147150
#[allow(clippy::let_unit_value)]
148151
let _ = AssertFits::<F, STACK_SIZE>::ASSERT;
149152

150-
// Since we have the static assert above, we know this will not fail. This means we could
151-
// use `unwrap_unchecked` here. The extra `unsafe` code probably isn't worth it since very
152-
// likely the compiler will be able to make that optimization for us, this comment is here
153-
// as a reminder that we can change it if for some reason this ends up being a performance
154-
// issue in the future.
155-
Self::try_from(future).unwrap_or_else(|f| {
156-
match (Self::has_alignment_for_val(&f), Self::has_space_for_val(&f)) {
157-
(false, false) => panic!(
158-
"cannot create StackFuture, required size is {}, available space is {}; required alignment is {} but maximum alignment is {}",
159-
mem::size_of_val(&f),
160-
STACK_SIZE,
161-
mem::align_of::<F>(),
162-
mem::align_of::<Self>()
163-
),
164-
(false, true) => panic!(
165-
"cannot create StackFuture, required alignment is {} but maximum alignment is {}",
166-
mem::align_of::<F>(),
167-
mem::align_of::<Self>()
168-
),
169-
(true, false) => panic!(
170-
"cannot create StackFuture, required size is {}, available space is {}",
171-
mem::size_of_val(&f),
172-
STACK_SIZE
173-
),
174-
// If we have space and alignment, then `try_from` would have succeeded
175-
(true, true) => unreachable!(),
176-
}
177-
})
153+
Self::try_from(future).unwrap()
178154
}
179155

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

213189
Ok(result)
214190
} else {
215-
Err(future)
191+
Err(IntoStackFutureError::new::<Self>(future))
216192
}
217193
}
218194

@@ -231,7 +207,7 @@ impl<'a, T, const STACK_SIZE: usize> StackFuture<'a, T, { STACK_SIZE }> {
231207
where
232208
F: Future<Output = T> + Send + 'a, // the bounds here should match those in the _phantom field
233209
{
234-
Self::try_from(future).unwrap_or_else(|future| Self::from(Box::pin(future)))
210+
Self::try_from(future).unwrap_or_else(|future| Self::from(Box::pin(future.into_future())))
235211
}
236212

237213
/// A wrapper around the inner future's poll function, which we store in the poll_fn field
@@ -338,5 +314,106 @@ impl<F, const STACK_SIZE: usize> AssertFits<F, STACK_SIZE> {
338314
};
339315
}
340316

317+
/// Captures information about why a future could not be converted into a [`StackFuture`]
318+
///
319+
/// It also contains the original future so that callers can still run the future in error
320+
/// recovery paths, such as by boxing the future instead of wrapping it in [`StackFuture`].
321+
pub struct IntoStackFutureError<F> {
322+
/// The size of the StackFuture we tried to convert the future into
323+
maximum_size: usize,
324+
/// The StackFuture's alignment
325+
maximum_alignment: usize,
326+
/// The future that was attempted to be wrapped
327+
future: F,
328+
}
329+
330+
impl<F> IntoStackFutureError<F> {
331+
fn new<Target>(future: F) -> Self {
332+
Self {
333+
maximum_size: mem::size_of::<Target>(),
334+
maximum_alignment: mem::align_of::<Target>(),
335+
future,
336+
}
337+
}
338+
339+
/// Returns true if the target [`StackFuture`] was too small to hold the given future.
340+
pub fn insufficient_space(&self) -> bool {
341+
self.maximum_size < mem::size_of_val(&self.future)
342+
}
343+
344+
/// Returns true if the target [`StackFuture`]'s alignment was too small to accommodate the given future.
345+
pub fn alignment_too_small(&self) -> bool {
346+
self.maximum_alignment < mem::align_of_val(&self.future)
347+
}
348+
349+
/// Returns the alignment of the wrapped future.
350+
pub fn required_alignment(&self) -> usize {
351+
mem::align_of_val(&self.future)
352+
}
353+
354+
/// Returns the size of the wrapped future.
355+
pub fn required_space(&self) -> usize {
356+
mem::size_of_val(&self.future)
357+
}
358+
359+
/// Returns the alignment of the target [`StackFuture`], which is also the maximum alignment
360+
/// that can be wrapped.
361+
pub const fn available_alignment(&self) -> usize {
362+
self.maximum_alignment
363+
}
364+
365+
/// Returns the amount of space that was available in the target [`StackFuture`].
366+
pub const fn available_space(&self) -> usize {
367+
self.maximum_size
368+
}
369+
}
370+
371+
impl<F: Future> IntoFuture for IntoStackFutureError<F> {
372+
type Output = F::Output;
373+
374+
type IntoFuture = F;
375+
376+
/// Returns the underlying future that caused this error
377+
///
378+
/// Can be used to try again, either by directly awaiting the future, wrapping it in a `Box`,
379+
/// or some other method.
380+
fn into_future(self) -> Self::IntoFuture {
381+
self.future
382+
}
383+
}
384+
385+
impl<F> Display for IntoStackFutureError<F> {
386+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
387+
match (self.alignment_too_small(), self.insufficient_space()) {
388+
(true, true) => write!(f,
389+
"cannot create StackFuture, required size is {}, available space is {}; required alignment is {} but maximum alignment is {}",
390+
self.required_space(),
391+
self.available_space(),
392+
self.required_alignment(),
393+
self.available_alignment()
394+
),
395+
(true, false) => write!(f,
396+
"cannot create StackFuture, required alignment is {} but maximum alignment is {}",
397+
self.required_alignment(),
398+
self.available_alignment()
399+
),
400+
(false, true) => write!(f,
401+
"cannot create StackFuture, required size is {}, available space is {}",
402+
self.required_space(),
403+
self.available_space()
404+
),
405+
// If we have space and alignment, then `try_from` would have succeeded
406+
(false, false) => unreachable!(),
407+
}
408+
}
409+
}
410+
411+
impl<F> Debug for IntoStackFutureError<F> {
412+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
413+
// Forward to the Display implementation
414+
write!(f, "{self}")
415+
}
416+
}
417+
341418
#[cfg(test)]
342419
mod tests;

src/tests.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use futures::Future;
77
use futures::SinkExt;
88
use futures::Stream;
99
use futures::StreamExt;
10+
use std::future::IntoFuture;
1011
use std::sync::Arc;
1112
use std::task::Context;
1213
use std::task::Wake;
@@ -92,7 +93,10 @@ fn test_size_failure() {
9293
buf[0]
9394
};
9495

95-
assert!(StackFuture::<_, 4>::try_from(f).is_err());
96+
match StackFuture::<_, 4>::try_from(f) {
97+
Ok(_) => panic!("conversion to StackFuture should not have succeeded"),
98+
Err(e) => assert!(e.insufficient_space()),
99+
}
96100
}
97101

98102
#[test]
@@ -127,7 +131,10 @@ fn test_alignment_failure() {
127131
Poll::Pending
128132
}
129133
}
130-
assert!(StackFuture::<'_, _, 1016>::try_from(BigAlignment(42)).is_err());
134+
match StackFuture::<'_, _, 1016>::try_from(BigAlignment(42)) {
135+
Ok(_) => panic!("conversion to StackFuture should not have succeeded"),
136+
Err(e) => assert!(e.alignment_too_small()),
137+
}
131138
}
132139

133140
#[cfg(feature = "alloc")]
@@ -189,7 +196,9 @@ fn try_from() {
189196

190197
match StackFuture::<_, 10>::try_from(big_future) {
191198
Ok(_) => panic!("try_from should not have succeeded"),
192-
Err(big_future) => assert!(StackFuture::<_, 1500>::try_from(big_future).is_ok()),
199+
Err(big_future) => {
200+
assert!(StackFuture::<_, 1500>::try_from(big_future.into_future()).is_ok())
201+
}
193202
};
194203
}
195204

0 commit comments

Comments
 (0)