Not planned
Description
Proposal
Problem statement
I propose to add more helpers to the std::future::Future
trait to make it easier to use for async code.
Motivating examples or use cases
futures_util::FutureExt
is widely used in async code as the stdlib does not provide the helper functions needed for many async crate.
std::iter::Iterator
on the other hand, has lots of helper functions and thus most crates don't need to pull in any extra dependencies to use it, as compared to Future
.
Solution sketch
I propose to add the following provided trait methods, similar to how Iterator
works to enable simple usage and ability to override for better/more efficient implementation:
impl Future {
/// Wrap the future in a Box, pinning it.
fn boxed<'a>(self) -> Pin<Box<dyn Future<Output = Self::Output> + Send + 'a>>
where
Self: Sized + Send + 'a;
/// Wrap the future in a Box, pinning it.
///
/// Similar to `boxed`, but without the `Send` requirement.
fn boxed_local<'a>(self) -> Pin<Box<dyn Future<Output = Self::Output> + 'a>>
where
Self: Sized + 'a;
}
And join for array/tuple
impl<T, const N: usize> [T; N]
where
T: Future
{
async fn join(self) -> [T::Output; N];
}
impl<T, O, R, const N: usize> [T; N]
where
T: Future<Output = O>,
O: Try<Residual = R>,
R: Residual<[R::Output; N]>,
{
async fn try_join<U>(self) -> <R as Residual<[R::Output; N]>>::TryType;
}
impl<T1, ...> (T1, ...)
where
T1, ...: Future
{
async fn join(self) -> (T1::Output, ...);
}
impl<T1, ..., O1, ..., R1, ..., const N: usize> (T1, ...)
where
T1, ...: Future<Output = O>,
O1, ...: Try<Residual = R>,
R1, ...: Residual<(R1::Output, ...)>,
{
async fn try_join<U>(self) -> <R as Residual<(R1::Output, ...)>>::TryType;
}
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
[-]Add more helpers to `std::future::Future` trait[/-][+]Add more helpers to `std::future::Future` trait and add `FusedFuture`[/+][-]Add more helpers to `std::future::Future` trait and add `FusedFuture`[/-][+]Add more helpers to `std::future::Future` trait and `FusedFuture`[/+]taiki-e commentedon May 29, 2025
I would recommend reading past discussions on this (e.g., rust-lang/rust#111347). There have been several people who have suggested such a thing in the past, but none of them were accepted because of several unresolved questions.
taiki-e commentedon May 29, 2025
Except for what already pointed out in the past discussion I linked above, the things I was wondering about are:
the8472 commentedon May 29, 2025
Note that with supertrait item shadowing v2 at least the name overloading should become less problematic.
[-]Add more helpers to `std::future::Future` trait and `FusedFuture`[/-][+]Add more helpers for `Future`[/+]NobodyXu commentedon May 29, 2025
Thanks, I've made some changes:
Future::map
,Future::flatten
andFuture::then
since using async/await is good enough, forFuture::then
it also introduces the complexity of async closureFuture::now_or_never
withimpl<T> From<Poll<T>> for Option<T>
, as the latter is simpler without cancellation issue (could be a separate api-change?)Future::pinned
withFuture::poll_unpinned
, I previously put apinned
since I believed it was more versatile (can be used withnow_or_never
)I think it might still be useful for manually wrapping multiple future, i.e. join in a specific order, with
FusedFuture
stdlib can avoid adding an extraOption
if the future is already fused via specialization (IIRC stdlib can use unstable specialization for implementation details).While most
async {}
automatically generated code is not fused, manually written futures often are fused, and it can be a nice property to expose.Future::boxed*
is useful, as it avoid the annoyingBox::pin(...) as Pin<Box<dyn Future + Send>>
which could be long and tedious, and it can also specialise to avoid reboxing, if the future is already boxedFuture::poll_unpin
is quite convenient as it is less tedious thanPin::new(&mut fut).poll(cx)
I think other methods of
FutureExt
is not quite useful.select!
is something quite complicated and still evolving with its API.std::async_iter
has other select functions covered.programmerjake commentedon May 29, 2025
there's a WIP language feature to allow calling
Pin<&mut impl Unpin>
methods with&mut
directly, sopoll_unpin
may be deprecated soon.NobodyXu commentedon May 30, 2025
Thanks, removed the function
m-ou-se commentedon Jun 3, 2025
cc @rust-lang/wg-async
Darksonn commentedon Jun 4, 2025
This really needs to be split up into several proposals.
Tokio explicitly decided to go away from
FusedFuture
in itsselect!
macro because it was believed to not be a good solution. Granted, Tokio'sselect!
also has issues, but I don't think going back toFusedFuture
is the answer.Why?
Why use a method over a macro? I think that macro syntax is nicer to read:
vs
I mean maybe I could be convinced otherwise, but like the other proposals here, I would like to see alternatives mentioned, and I would like to see a reason for why one approach is chosen over the alternatives.
taiki-e commentedon Jun 4, 2025
The problem is, as I said in the linked comment, that it cannot be correctly implemented without specializations. (Using specialization in std is not enough to do things correctly.)
Even if the std/compiler can optimize it, it will only leave something broken unless the implementation on which the optimization depends is correctly implemented.
NobodyXu commentedon Jun 5, 2025
Suppose you have a function taking an array of arbitrary, using
join!
simply won't work as you don't know the length of the array:And using
join!
adds a bunch of code to the caller in an opaque and hard to audit/understand way (macro likepin!
could even add local variables), where as a function has a clear boundary.Having a
join
methods for tuple would be ergonomic, if rust ever has varadic tuple that can be passed to generic functions, compared to macro that inserts code into the caller:The problem I just realized though is
[T]::join
conflicts with[T; N]::join
, so maybe a different name is required.Thanks, so it'd require
unsafe
for it to even work...in that case it indeed isn't very useful, I've removed itI think
Poll
actually matchesOption
quite well: Either you have a return value or you don't, that'd be useful for cases where future is only poll once (FutureExt::now_or_never
).taiki-e commentedon Jun 5, 2025
If we adding a helper for such case, I think it is preferable that this be an explicit method rather than a From implementation.
But I'm not sure that is really necessary, and also note that now_or_never uses noop_context so it does not work just using it.
NobodyXu commentedon Jun 5, 2025
That's true, I've removed the From impl from proposal
traviscross commentedon Jun 10, 2025
Among wg-async, we discussed this on Zulip. In the discussion, we agreed with @Darksonn that this proposes too much to be considered together.
We have general reservations about adding many helpers to
Future
; many of us believe it was a mistake to add so many methods toIterator
and would prefer we find other solutions for our other traits, especially as it's possible that we'll end up with many similar traits (e.g.TryIterator
,LendingIterator
, etc.). Concerns were also raised about adding inherent impls to tuples or arrays.We would like to solve some of the things that motivate proposals like this. It's probably not going to look like this, though, and is going to need more design and discussion in the working group, and the eventual direction will likely be influenced by work on the lang side that is ongoing.
Thanks to the author for proposing this. It's always good to think about these things. But I'd recommend closing this.
See also:
AsyncIterator
helper functions #599AsyncIterator
helper functions #599