-
Notifications
You must be signed in to change notification settings - Fork 13k
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
Tracking Issue for poll.ready()? #89780
Comments
One trivial item, should the debug implementation for |
It doesn't matter much since it's unlikely that the Debug implementation for this type will be used much anyway, but the default |
Do we have a way of pinging async stakeholders here? There was a lot of discussion among several participants in #81050 for example. I can just pick out names from there, but if there's a better way... |
cc @rust-lang/wg-async-foundations |
Personally I find this less clear than the With the
|
You could write it this way: fn some_future_impl() -> Poll<Result<(), ()>> {
some_check()?.ready()?;
Poll::Ready(Ok(()))
}
fn some_stream_impl() -> Poll<Option<Result<(), ()>>> {
some_check()?.ready()?;
Poll::Ready(Some(Ok(())))
} Which makes it clearer IMO, because The fact that there are two ways to use |
I have to agree with @cramertj I think the |
@rustbot label +AsyncAwait-triaged |
In the coding experience aspect, since rust-analyzer supports custom postfix-snippets, just define a snippet like the following one, you can write "rust-analyzer.completion.snippets": {
"ready!": {
"postfix": "ready",
"body": [
"ready!(${receiver})",
],
"requires": "std::task::ready",
"description": "ready!()",
"scope": "expr",
}
} |
Drive by comment: I have to disagree with statements that |
@rfcbot merge |
Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
@rfcbot concern try impl contradicts existing try impls I'm really worried that introducing a Try impl on fn some_stream_impl() -> Poll<Option<Result<(), ()>>> {
some_check()?.ready()?;
Poll::Ready(Some(Ok(())))
}
// and
fn some_stream_impl() -> Poll<Option<Result<(), ()>>> {
some_check().ready()??;
Poll::Ready(Some(Ok(())))
} The first example attempts to more clearly communicate what is being propagated, but there's an assumption that cc @scottmcm |
I don't have sufficient experience with the Disallowing the current behaviour might be doable with an approach like the one in https://rust-lang.github.io/rfcs/3058-try-trait-v2.html#compatibility-with-accidental-interconversions-if-needed -- make a new trait for A big question here, though, might be if anything is using them in |
@yaahc Thank you for catching this. I was aware we had impls for Poll, but I had always naturally assumed that they would propagate Pending; the idea that they would instead propagate Ready would never have occurred to me. I agree that we need some way to disable those instances. An edition seems like a fine approach. |
I guess I'll speak up as the person who added those impls. I still think that adding them was critical to making the transition from Personally, I also still find the " You could disambiguate by entirely removing the ability to use |
@cramertj that makes sense and I do want to highlight that I only briefly spent time writing much async and haven't used that portion of the language much recently, particularly not to the point of writing Poll impls, so I definitely want to lean on y'all's experience as much as possible here. I do not have a great intuition for how much people do or don't like these Try impls on I talked to scottmcm a bit more about the ways we could fix this if we wanted to. He mentioned it above but basically making two Try traits, having one be permanently unstable for 2015-2021, then stabilizing the new one and the API in this issue in 2024. There was some confusion since he wasn't sure if we wanted to be able to use My question for the async experts in the room is, how big of an issue is this confusion really? I see a few options forward here and some pretty substantial tradeoffs:
I think its fair to say that (1) is the worst option here. But I'm not certain which is better between (2) and (3). Footnotes |
FWIW, I do favor option 3 here, and I think people's intuitions for what |
I think I agree but I really want to make sure we have a strong consensus from async users as well. My feeling is that if we pick (2) we're prioritizing short term improvements over long term, and I generally think the long term maximum has a much greater overall impact since we're talking about sacrificing usability for ~2.5 years for overall better(I think???) usability forever after. The idea of delaying |
Just brainstorming: A potential 1b would be to add the impl immediately, but add some as-yet-unknown lints or errors to help mitigate the cons. For example, we could have edition-specific lints or errors that look for specifically using As a sortof 3b that's also mixing in 1, we could maybe add a That latter one might end up just being like a transpose, as it might be able to just return a |
For those that prefer (3) here, what would the following code samples look like under your preferred replacement? These were (are?) common patterns in manual fn poll_next(...) -> Poll<Option<Result<T, E>>> { // and also `poll(...) -> Poll<Result<T, E>>`
// Return early with errors, don't propagate pending
let ...: Poll<T> = some_future.poll(cx)?
let ...: Poll<Option<T>> = some_child_stream.poll_next(cx)?;
// Return early on either errors or pending
let ...: T = ready!(some_future.poll(cx))?;
let ...: Option<T> = ready!(some_child_stream.poll_next(cx))?;
...
} |
Leaving aside bikeshed-y questions about the naming of functions, I'm gonna lean on @scottmcm's suggestion for having a transpose equivalent on fn poll_next(...) -> Poll<Option<Result<T, E>>> { // and also `poll(...) -> Poll<Result<T, E>>`
// Return early with errors, don't propagate pending
let ...: Poll<T> = some_future.poll(cx).bikeshed_transpose_result()?
let ...: Poll<Option<T>> = some_child_stream.poll_next(cx).bikeshed_transpose_result()?;
// Return early on either errors or pending
let ...: T = some_future.poll(cx).ready()?;
let ...: Option<T> = some_child_stream.poll_next(cx).ready()??;
...
} I think this is possibly clear enough. Edit: One more question actually @cramertj, in your example did you expect that the return type for |
@yaahc Can you explain in more detail how you imagine the |
One thing that came to mind that opens up as a possibility if some form of (3) happens, removing the Right now there's basically no trait restriction on residuals, and that has caused some confusion -- see the third unresolved question in #84277. Part of why that is is that the If we didn't have those, then a restriction like trait Try {
type Output;
type Residual: Residual<Self::Output, TryType = Self>;
...
} Could tie things together more strictly, making mistakes less likely and thus possibly also making it easier to understand since there'd be less freedom in how the types all relate to each other. Basically, it'd mean the all the standard library break/continue splits would be simple. Whereas right now the Now, I don't know for sure whether we'd want to do that, I don't want to decide it here, and I think that something similar might also be doable in the other solutions too. But @yaahc suggested I post more thoughts here 🙂 |
(NOT A CONTRIBUTION) In my opinion, this is worse than the ready macro in every way. I have a strong preference that the ready macro be stabilized and this API be removed from std. First, I second @cramertj's comment that low level poll methods return result the majority of the time, so the ready operation is being combined with Second, I think the ready operator needs to be put into its proper context. Poll methods are functionally a rarely used DSL for a specific category of low level code. The various special operations needed to understand and implement them (i.e. the apis in std::pin and std::task) are therefore not a part of ordinary Rust usage, but something people will need to understand to drop down into them. Therefore, being easy to learn is far more important than being elegant to write. The ready macro is easier to understand than this API in several ways. The first way its easier to understand is that it is far easier to read the source of. The ready macro is a simple match statement. To understand Poll::ready, first you read a method, which constructs a struct, then you have to understand that its the Try impl on the struct that you are interested in, then you have to know how the Try API is related to the ? match syntax. All of this is very opaque, and while documentation helps it is certainly less simple than the ready macro. The second is that it is composed of two operations (a method call and a ? operator) when it is only a single operator. There is no reason to ever called And I think that there is genuine merit to the Finally, when it comes to "creating a new Try in the next edition" I feel I must again raise that I do not think the project is taking churn seriously enough; this would be hugely disruptive for any project with poll implementations and make it much harder for them to transition editions. What justification is there to create this churn for so many of your users? I don't really understand the impetus behind these delays and ideas. Is there some user experience maintaining these methods that suggests the current ecosystem consensus is unsatisfactory and needs improvement? To me seems based on a theoretical desire for an API that matches the aesthetic sensibilities of the current libs team (use Try for everything, postfix everything, etc). I'd push for instead a more pragmatic attitude, returning to Graydon's original FAQ: "We do not prize expressiveness, minimalism or elegance above other goals ... We do not intend to be ... too dogmatic in any other sense. Trade-offs exist." Speaking as someone who maintains poll methods, I strongly prefer to stabilize the ecosystem's consensus API: the ready macro, which is simple and straightforward and has existed and been in use for years already. |
Going back to my original quote
The conversation may have gotten away from us but I don't think we ever progressed far enough into the follow up discussion to get to a point where anyone should be expected to have a fully fleshed out proposal. I pointed out an issue that I thought should block the API, scott suggested some potential paths to resolving that blocker, and since then nobody has taken those ideas and converted them into a proposal. So going back to your quote
I do not believe this is a fair characterization. You're mischaracterizing a brainstorming session as if it were the stance of the project, and not two individuals going back and forth on unblocking potential issues as they came up so that the conversation could continue. |
(NOT A CONTRIBUTION) @yaahc When I talk about the project's relationship to churn I don't mean this issue in particular, but the attitude I see expressed again and again that if you can find a technical way to meet the edition requirements, you can make a change that makes existing code invalid in the next edition. Instead, it should always be recognized and centrally considered that the social cost of invalidating existing code is high, and should only be done with a compelling reason. EDIT: I check my other notification and what I'm immediately shown is you advocating idiom churn as a general rule; what exactly are you protesting? What is the compelling reason for doing that, or for adding this API instead of stabilizing the API that everyone already is importing from futures or tokio? Here is the argument that has been made so far for this API in this and the PR that introduced it:
Seriously? |
@withoutboats I agree with you that the macro is overall the better solution given
is rather dismissive of other people's opinions. |
(NOT A CONTRIBUTION) Yes, I am dismissive of an argument for blocking stabilization of a widely used API and considering major idiom churn that has never been expressed in more convincing detail than "it looks better," "its quite nice," and "it seems better in every way." This should not be the standard for decision making in this project. |
Again, the comment you linked is my opinion, not the opinion of the libs team. If I was representing the stance of the libs team I would have used "we" instead of "I". I don't agree with your assessment that I was advocating for idiomatic churn. I was advocating for more strongly de-duplicating APIs when we add new APIs that supersede old APIs rather than just marking them deprecated and softly encouraging people to upgrade. In the quote you linked I specifically addressed the issue of churn
You mentioned "Instead, it should always be recognized and centrally considered that the social cost of invalidating existing code is high, and should only be done with a compelling reason.", and I feel like I did clearly outline a compelling reason in that case, we have an API that we're seeming likely to accept with suggestions to deprecate an existing API in the process, and assuming that deprecation does get accepted I advocated for limiting the number of distinct dialects of rust that develop and to maintain consistency in the language. The disconnect seems to be a difference in the recognized cost of invalidating existing code. I think with automatic migrations the upgrade cost of churn can be small, and that this small churn can be justified when it reduces the surface area of the language long term, since either way you still have to pay the cost of interacting with and understanding both idioms. You seem to think there are additional concerns I'm not considering, could you elaborate on those other costs? |
(NOT A CONTRIBUTION)
We shouldn't conflate these two issues, there's no doubt the Just because you can mechanically migrate code doesn't mean that the social cost becomes 0. At the time we introduced the mechanism of This churn can be justified, but justified it must be. Here the justification seems to be an unquestioned commitment to the idea that postfix is always better, that macros are always worse, that Given the argument I have already made at length, I think both the ready method and changing Even setting aside how egregious this particular issue is, no I do not think "it reduces the surface of the std API" is ever a good enough reason to remove an API, or even to lint-deprecate it. The old API has to be in some way substantially inferior to the new API, prone to error, less efficient, something like this - not simple an idiom the current group around the project has decided they don't care for. |
At the expense of possibly creating yet another parallel track to an already non-trivial discussion, I would like to point out that |
@withoutboats I think you're providing valuable feedback, and you make a lot of great points, but I agree with Jane that you're mischaracterizing what's happening by saying
That's not quite what happened for Rust 2021 at all. We've been extremely careful, and rejected quite a few ideas based on the same points that you're making in this thread. The transition to Rust 2021 has been very smooth. I have no reason to believe this will be any different for the next edition. Just because we're exploring some (sometimes wild) ideas, does not mean we'll not be careful when making decisions. Importantly, much of such exploration happens by individuals, while decisions that affect stable Rust are made by the full team through FCP. It'd not be helpful if every idea any of us explore is seen as a team decision. For the I've nominated the issue for discussion by the library api team. There's a ton of new insights to be considered, for which I thank you. If you have any concerns about the general way the team operates or the direction the standard library is going into, I'd love to hear them. But dismissing the (sometimes controversial) ideas of one of our most ambitious and hard working team members is not a good look, and does not benefit the quality of future Rust in any way. |
@rust-lang/wg-async Hey Async WG! We (@rust-lang/libs-api) would like your input and ideally a recommendation on what to do here. There are good arguments for and against both |
We've been discussing this on Zulip, and it seems we're seeing many of the same tradeoffs you are. Though it seems so far what we mostly agree on though is that stabilizing something, with clear semantics, in the nearer term is the higher order bit. Personally I do want to explicitly call out that just because I'm choosing to support the stabilization of Footnotes
|
Yep, @yoshuawuyts's comment sums up the WG's discussion pretty well. I just want to add that I am pretty unsure about using |
(NOT A CONTRIBUTION) Another remark has occurred to me that I realized hasn't been made quite explicit here: it's not just that For this reason, when returning This is what it really means to be different between saying a future has errored and saying it is pending. It has a huge impact on the code you have to write, and it should be syntactically distinct to highlight what is going on. |
Am I right in thinking that this doesn't just apply to futures that return I'm basing this off of this line in https://doc.rust-lang.org/stable/std/future/trait.Future.html#tymethod.poll
|
(NOT A CONTRIBUTION) Yea, that's correct. It's just we don't have a need for an early return operator for the successfully completed case. But since we do have use for an easy way to early return for both completed with error and pending, an important distinction about the pending case is they need to be left in a state to be called again and pick up where they left off. |
I've started an FCP on #70922 to see if there's consensus on stabilizing the |
Canceling the FCP on this in favor of @rfcbot cancel |
@joshtriplett proposal cancelled. |
The FCP on #70922 has completed and the |
Remove unstable `Poll::ready` Based on the discussion in rust-lang#89780, this API is problematic and would likely require changes over an edition. Now that `task::ready!` is stabilized, this seems unlikely to happen, so I think we should just go ahead and remove it. ACP: rust-lang/libs-team#214
This API was removed in #107060 in favor of |
Feature gate:
#![feature(poll_ready)]
This is a tracking issue for the
core::task::Poll::ready
method, which combined with?
potentially supplants theready!
macro of #70922.Public API
Steps / History
Poll::ready
and revert stabilization oftask::ready!
#89651Poll::ready
libs-team#214Poll::ready
#107060Unresolved Questions
The text was updated successfully, but these errors were encountered: