-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Future::poll_once
#92116
Add Future::poll_once
#92116
Conversation
r? @scottmcm (rust-highfive has picked a reviewer for you, use r? to override) |
The job Click to see the possible cause of the failure (guessed by this bot)
|
If you use this function without considering the cancellation safety of the underlying future, it may cause various bugs (rust-lang/futures-rs#2452 (review)). So, I do not agree with adding this without caveats. |
cc @rust-lang/wg-async-foundations |
I don't mind this feature too much, but it is worth noting the applications of this are definitely limited. We never added this functionality to I agree with @taiki-e that the "drop in-place" nature of Overall it seems |
I agree with both concerns (level of motivation and drop/cancellation safety). I think at the least, the docs need expanding to properly explain what this function does, why it is different to |
Marking this for team discussion, since I don't have enough context to know whether to take this. |
I'd love to see a specific use case for this as well. |
I don't think we should begin piecemeal importing combinators like this into std. I think there should be a design RFC first on if and how to transition combinator methods from futures-rs and tokio into std. Until then, functions like this can exist in those external crates. |
Ping from triage: |
@yoshuawuyts This and #93176 are different features, not like a superset.
Note that you need to have a feature to get the context in the async function/block to do the same in this way. |
I see. I've gone and deleted my comment further confusion. Apologies for the noise. |
I don't know that this should be in std, but I did encounter a case where I wanted it. Specifically, I have some functionality that sometimes blocks and sometimes doesn't, depending on its input. That function needs to be async, but in the cases where it doesn't block, it gets called from non-async functions. I manually invoke |
/// documentation for more. | ||
#[unstable(feature = "future_poll_once", issue = "92115")] | ||
pub struct PollOnce<F> { | ||
pub(crate) future: F, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idea to support ?Unpin
:
pub(crate) future: F, | |
pub(crate) future: Option<F>, |
(and a impl<F> Unpin for PollOnce<F>
)
F: Future + Unpin, | ||
{ | ||
type Output = Poll<F::Output>; | ||
|
||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { | ||
Poll::Ready(Pin::new(&mut self.future).poll(cx)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
F: Future + Unpin, | |
{ | |
type Output = Poll<F::Output>; | |
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { | |
Poll::Ready(Pin::new(&mut self.future).poll(cx)) | |
} | |
F: Future, | |
{ | |
type Output = Poll<F::Output>; | |
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { | |
let future = pin!(self.future.take().expect("Polled after completion")); | |
Poll::Ready(future.poll(cx)) | |
} |
If the dependency on a macro is deemed unsatisfactory to begin with, we could always hand-roll a Pin::new_unchecked()
. This is also a nice instance to showcase the safe .pinned()
(🚲 🏠) closure-based combinator:
self.future
.take()
.expect("Polled after completion")
.pinned(|future| Poll::Ready(future.poll(cx)))
where | ||
Self: Sized, | ||
{ | ||
PollOnce { future: self } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PollOnce { future: self } | |
PollOnce { future: Some(self) } |
Ping from triage: |
One that recently came up was using async in the context of a GUI/TUI event loop. One might want to periodically check a resource's status without blocking (yielding). |
But |
I mean, since |
Oh I see. It's starting to make more sense to me why this might sometimes be useful. I think it'd be helpful if we could include examples of this usage as part of the docs examples. Also after some github searching I found that there is also prior art for |
@ibraheemdev |
Waiting for a decision on whether this should be added before putting in more work @JohnCSimon |
We discussed this in today's @rust-lang/libs-api meeting. We didn't have an objection to adding this, since it does have uses (per #92116 (comment) ). But we'd like confirmation that this is the correct API; should it be non-consuming by default, rather than taking Also, is this the right name? cc @yoshuawuyts |
I think the current API is correct as written. If the method did not consume It's interesting to think of what this might look like. Now that we seem to have a promising way to stack-pin types ( #![feature(pin_macro)]
use std::task::Poll;
use std::pin::Pin;
// current proposed API:
let pinned_fut = pin!(future::ready(()));
assert_eq!(pinned_fut.as_mut().poll_once(), Poll::Ready(()));
// using a pinned `Self`-type in the signature:
let pinned_fut = pin!(future::ready(()));
assert_eq!(pinned_fut.poll_once(), Poll::Ready(())); // note the omission of `as_mut` here. Going even further though: I think the main innovation of #![feature(pin_macro)]
use std::task::{self, Poll};
use std::pin::Pin;
// using a function to get the underlying `Context` object:
let pinned_fut = pin!(future::ready(()));
let cx = task::context().await; // this method is made up, it just returns the underlying `Context`
assert_eq!(pinned_fut.poll(cx), Poll::Ready(())); The main benefit I see with this approach is that it would compose well with other Footnotes
|
I agree that it should consume self. When you consume self, you support both the use-cases of consuming it and not consuming it (by calling it on a |
I don't really think so because of the ambiguity between this and the synchronous let f = pin!(f);
f.poll(task::context!()); // poll_once
f.poll(task::noop_context()); // now_or_never |
What is the status of this feature? |
@ibraheemdev - can you comment on this? |
@ibraheemdev @rustbot label: +S-inactive |
If it is OK to the OP, I will re-create this PR. |
@c410-f3r see #92116 (comment), I think this is a better way forward. |
I was hoping to experiment this feature as an user (not as a Rustc developer) but since the API requires more discussion (which is a bit strange for nightly), then I give up due to the lack of personal energy. |
This PR adds
Future::poll_once
, which returns the output of polling a future once.I'm not sure what a good name for this would be (try_await?). It isn't polling once any more than
Future::poll
is. Maybe keeping it as a macro/free-standing function is the best way to resolve the ambiguity?Prior Art: https://docs.rs/futures/latest/futures/macro.poll.html
(Can someone on the team cc/ @rust-lang/wg-async-foundations?)