Skip to content

feat(util): introduce TryFutureBody<F, B> - #177

Merged
cratelyn merged 4 commits into
hyperium:masterfrom
cratelyn:try-future-body
Sep 1, 2026
Merged

feat(util): introduce TryFutureBody<F, B>#177
cratelyn merged 4 commits into
hyperium:masterfrom
cratelyn:try-future-body

Conversation

@cratelyn

@cratelyn cratelyn commented Aug 21, 2026

Copy link
Copy Markdown
Member

fixes #157.

this commit introduces a new utility to http-body-util, permitting
callers to treat a fallible Future<Output = Result<Body, _>> as a
body. while the future is still pending, the inner future will be
polled. once the future yields a body, the body will then be polled for
its contents.

one important difference in the code in this commit as compared to the
original snippet proposed in #157 is that if the future fails and yields
an error, the error will be propagated and the body will be marked as
having failed.

like the Either<L, R> middleware, this adapter type works around some
minor limitations in pin-project-lite. namely, enum tuple variants are
not supported, and doc-comments (required here per the missing_docs
lint enforced in this library
) also were not parsed properly.

a proj submodule contains code derived from the output generated by
the pin_project! macro, with some additional commentary added to be
thorough about noting safety with respect to Pin<T> projection.

a small test suite is included to show that error propagation works as
expected, hints work correctly, and that data from the inner body is
returned correctly.

one other detail about the code in this commit worth calling out is
that this middleware is named TryFutureBody<F, B> rather than
FutureBody<F, B>. because this works with futures whose output is a
fallible Result<B, E>, it felt like a forward-compatible choice to
choose this name instead. that will permit the future addition of a
FutureBody<F, B> that wraps futures that emit a plain B body. that
is not included in this commit, so as to facilitate review, but can be
added as a simple follow-up to this proposal.

fixes hyperium#157.

this commit introduces a new utility to `http-body-util`, permitting
callers to treat a fallible `Future<Output = Result<Body, _>>` as a
body. while the future is still pending, the inner future will be
polled. once the future yields a body, the body will then be polled for
its contents.

one important difference in the code in this commit as compared to the
original snippet proposed in hyperium#157 is that if the future fails and yields
an error, the error will be propagated and the body will be marked as
having failed.

like the `Either<L, R>` middleware, this adapter type works around some
minor limitations in `pin-project-lite`. namely, enum tuple variants are
not supported, and doc-comments (_required here per the `missing_docs`
lint enforced in this library_) also were not parsed properly.

a `proj` submodule contains code derived from the output generated by
the `pin_project!` macro, with some additional commentary added to be
thorough about noting safety with respect to `Pin<T>` projection.

a small test suite is included to show that error propagation works as
expected, hints work correctly, and that data from the inner body is
returned correctly.

one _other_ detail about the code in this commit worth calling out is
that this middleware is named `TryFutureBody<F, B>` rather than
`FutureBody<F, B>`. because this works with futures whose output is a
fallible `Result<B, E>`, it felt like a forward-compatible choice to
choose this name instead. that will permit the future addition of a
`FutureBody<F, B>` that wraps futures that emit a plain `B` body. that
is not included in this commit, so as to facilitate review, but can be
added as a simple follow-up to this proposal.

Signed-off-by: katelyn martin <git@katelyn.world>
@cratelyn
cratelyn marked this pull request as ready for review August 21, 2026 22:12
@seanmonstar

Copy link
Copy Markdown
Member

Reminds me of tower's FutureService. Sounds good.

Perhaps it should be an opaque struct publicly, and the enum variants internal?

this prevents future additions or alterations to the inner mechanics of
`TryFutureBody<F, B>` from being breaking changes w.r.t. semantic
versioning.

this applies a review suggestion from:
hyperium#177 (comment).

Signed-off-by: katelyn martin <git@katelyn.world>
this helps explain the state transitions of the `Inner` enum.

Signed-off-by: katelyn martin <git@katelyn.world>
@cratelyn

Copy link
Copy Markdown
Member Author

Reminds me of tower's FutureService. Sounds good.

Perhaps it should be an opaque struct publicly, and the enum variants internal?

that's a great idea, that prevents changes to the enum from becoming breaking changes.

3fb9507 applies that, and cc8bc8b introduces a small ascii diagram to outline the state machine's transitions.

@cratelyn
cratelyn marked this pull request as draft August 31, 2026 19:01
@cratelyn

Copy link
Copy Markdown
Member Author

i believe this can be written without relying on any handwritten unsafe code. i'm going to rework this slightly to avoid the manually written pin projection code before marking it as ready for another round of review.

Signed-off-by: katelyn martin <git@katelyn.world>
@cratelyn
cratelyn marked this pull request as ready for review August 31, 2026 19:14
Comment thread http-body-util/src/future.rs Outdated
Comment on lines -135 to -160
impl<F, B> TryFutureBody<F, B> {
/// Returns an [`InnerProj<'pin, F, B>`] projection.
///
/// This is used internally by [`TryFutureBody<F, B>`] to access its inner future and body.
pub(super) fn project<'pin>(self: Pin<&'pin mut Self>) -> InnerProj<'pin, F, B> {
// Safety:
//
// We never move the inner future, or the inner body, out of the mutable reference
// we receive from `Pin::get_unchecked_mut()`. We project their "pinnedness" forwards
// into a `Pin<&mut F>` or a `Pin<&mut B>`, respectively. If the body is finished,
// there is no data that could be moved out.
//
// - https://doc.rust-lang.org/std/pin/struct.Pin.html#method.get_unchecked_mut
//
// For more information on structural pinning, see:
// <https://doc.rust-lang.org/std/pin/index.html#projections-and-structural-pinning>
unsafe {
let Self { inner } = self.get_unchecked_mut();
match inner {
Inner::Future(fut) => InnerProj::Future(Pin::new_unchecked(fut)),
Inner::Body(body) => InnerProj::Body(Pin::new_unchecked(body)),
Inner::Failed => InnerProj::Failed,
}
}
}
}

@cratelyn cratelyn Aug 31, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code is removed in 800c889, the changes in 3fb9507 mean that we can use pin_project_lite::pin_project without any issues. avoiding tuple variants for our Inner enum is a small price to pay for avoiding any bespoke unsafe code.

@cratelyn cratelyn left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬

@cratelyn
cratelyn merged commit b915a74 into hyperium:master Sep 1, 2026
8 checks passed
@cratelyn
cratelyn deleted the try-future-body branch September 2, 2026 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Convert Future<Body> into Body

2 participants