Skip to content

Tracking issue for async/await (RFC 2394) #50547

Closed

Description

@withoutboats

This is the tracking issue for RFC 2394 (rust-lang/rfcs#2394), which adds async and await syntax to the language.

I will be spearheading the implementation work of this RFC, but would appreciate mentorship as I have relatively little experience working in rustc.

TODO:

Unresolved questions:

Activity

  1. added
    B-RFC-approvedBlocker: Approved by a merged RFC but not yet implemented.
    T-langRelevant to the language team
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on May 8, 2018
  2. rpjohnst commented on May 8, 2018

    @rpjohnst
    Contributor

    The discussion here seems to have died down, so linking it here as part of the await syntax question: https://internals.rust-lang.org/t/explicit-future-construction-implicit-await/7344

  3. withoutboats commented on May 9, 2018

    @withoutboats
    ContributorAuthor

    Implementation is blocked on #50307.

  4. Pzixel commented on May 10, 2018

    @Pzixel

    About syntax: I'd really like to have await as simple keyword. For example, let's look on a concern from the blog:

    We aren’t exactly certain what syntax we want for the await keyword. If something is a future of a Result - as any IO future likely to be - you want to be able to await it and then apply the ? operator to it. But the order of precedence to enable this might seem surprising - await io_future? would await first and ? second, despite ? being lexically more tightly bound than await.

    I agree here, but braces are evil. I think it's easier to remember that ? has lower precedence than await and end with it:

    let foo = await future?

    It's easier to read, it's easier to refactor. I do believe it's the better approach.

    let foo = await!(future)?

    Allows to better understand an order in which operations are executed, but imo it's less readable.

    I do believe that once you get that await foo? executes await first then you have no problems with it. It's probably lexically more tied, but await is on the left side and ? is on the right one. So it's still logical enough to await first and handle Result after it.


    If any disagreement exist, please express them so we can discuss. I don't understanda what's silent downvote stands for. We all wish good to the Rust.

  5. alexreg commented on May 10, 2018

    @alexreg
    Contributor

    I have mixed views on await being a keyword, @Pzixel. While it certainly has an aesthetic appeal, and is perhaps more consistent, given async is a keyword, "keyword bloat" in any language is a real concern. That said, does having async without await even make any sense, feature wise? If it does, perhaps we can leave it as is. If not, I'd lean towards making await a keyword.

  6. Nemo157 commented on May 10, 2018

    @Nemo157
    Contributor

    I think it's easier to remember that ? has lower precedence than await and end with it

    It might be possible to learn that and internalise it, but there's a strong intuition that things that are touching are more tightly bound than things that are separated by whitespace, so I think it would always read wrong on first glance in practice.

    It also doesn't help in all cases, e.g. a function that returns a Result<impl Future, _>:

    let foo = await (foo()?)?;
  7. rpjohnst commented on May 10, 2018

    @rpjohnst
    Contributor

    The concern here is not simply "can you understand the precedence of a single await+?," but also "what does it look like to chain several awaits." So even if we just picked a precedence, we would still have the problem of await (await (await first()?).second()?).third()?.

    A summary of the options for await syntax, some from the RFC and the rest from the RFC thread:

    • Require delimiters of some kind: await { future }? or await(future)? (this is noisy).
    • Simply pick a precedence, so that await future? or (await future)? does what is expected (both of these feel surprising).
    • Combine the two operators into something like await? future (this is unusual).
    • Make await postfix somehow, as in future await? or future.await? (this is unprecedented).
    • Use a new sigil like ? did, as in future@? (this is "line noise").
    • Use no syntax at all, making await implicit (this makes suspension points harder to see). For this to work, the act of constructing a future must also be made explicit. This is the subject of the internals thread I linked above.

    That said, does having async without await even make any sense, feature wise?

    @alexreg It does. Kotlin works this way, for example. This is the "implicit await" option.

  8. alexreg commented on May 10, 2018

    @alexreg
    Contributor

    @rpjohnst Interesting. Well, I'm generally for leaving async and await as explicit features of the language, since I think that's more in the spirit of Rust, but then I'm no expert on asynchronous programming...

  9. Pzixel commented on May 10, 2018

    @Pzixel

    @alexreg async/await is really nice feature, as I work with it on day-to-day basis in C# (which is my primary language). @rpjohnst classified all possibilities very well. I prefer the second option, I agree on others considerations (noisy/unusual/...). I have been working with async/await code for last 5 years or something, it's really important to have such a flag keywords.

    @rpjohnst

    So even if we just picked a precedence, we would still have the problem of await (await (await first()?).second()?).third()?.

    In my practice you never write two await's in one line. In very rare cases when you need it you simply rewrite it as then and don't use await at all. You can see yourself that it's much harder to read than

    let first = await first()?;
    let second = await first.second()?;
    let third = await second.third()?;

    So I think it's ok if language discourages to write code in such manner in order to make the primary case simpler and better.

    hero away future await? looks interesting although unfamiliar, but I don't see any logical counterarguments against that.

  10. rpjohnst commented on May 10, 2018

    @rpjohnst
    Contributor

    In my practice you never write two await's in one line.

    But is this because it's a bad idea regardless of the syntax, or just because the existing await syntax of C# makes it ugly? People made similar arguments around try!() (the precursor to ?).

    The postfix and implicit versions are far less ugly:

    first().await?.second().await?.third().await?
    first()?.second()?.third()?
  11. Pzixel commented on May 10, 2018

    @Pzixel

    But is this because it's a bad idea regardless of the syntax, or just because the existing await syntax of C# makes it ugly?

    I think it's a bad idea regardless of the syntax because having one line per async operation is already complex enough to understand and hard to debug. Having them chained in a single statement seems to be even worse.

    For example let's take a look on real code (I have taken one piece from my project):

    [Fact]
    public async Task Should_UpdateTrackableStatus()
    {
    	var web3 = TestHelper.GetWeb3();
    	var factory = await SeasonFactory.DeployAsync(web3);
    	var season = await factory.CreateSeasonAsync(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(1));
    	var request = await season.GetOrCreateRequestAsync("123");
    
    	var trackableStatus = new StatusUpdate(DateTimeOffset.UtcNow, Request.TrackableStatuses.First(), "Trackable status");
    	var nonTrackableStatus = new StatusUpdate(DateTimeOffset.UtcNow, 0, "Nontrackable status");
    
    	await request.UpdateStatusAsync(trackableStatus);
    	await request.UpdateStatusAsync(nonTrackableStatus);
    
    	var statuses = await request.GetStatusesAsync();
    
    	Assert.Single(statuses);
    	Assert.Equal(trackableStatus, statuses.Single());
    }

    It shows that in practice it doesn't worth to chain awaits even if syntax allows it, because it would become completely unreadable await just makes oneliner even harder to write and read, but I do believe it's not the only reason why it's bad.

    The postfix and implicit versions are far less ugly

    Possibility to distinguish task start and task await is really important. For example, I often write code like that (again, a snippet from the project):

    public async Task<StatusUpdate[]> GetStatusesAsync()
    {
    	int statusUpdatesCount = await Contract.GetFunction("getStatusUpdatesCount").CallAsync<int>();
    	var getStatusUpdate = Contract.GetFunction("getStatusUpdate");
    	var tasks = Enumerable.Range(0, statusUpdatesCount).Select(async i =>
    	{
    		var statusUpdate = await getStatusUpdate.CallDeserializingToObjectAsync<StatusUpdateStruct>(i);
    		return new StatusUpdate(XDateTime.UtcOffsetFromTicks(statusUpdate.UpdateDate), statusUpdate.StatusCode, statusUpdate.Note);
    	});
    
    	return await Task.WhenAll(tasks);
    }

    Here we are creating N async requests and then awaiting them. We don't await on each loop iteration, but firstly we create array of async requests and then await them all at once.

    I don't know Kotlin, so maybe they resolve this somehow. But I don't see how you can express it if "running" and "awaiting" the task is the same.


    So I think that implicit version is a no-way in even much more implicit languages like C#.
    In Rust with its rules that doesn't even allow you to implicitly convert u8 to i32 it would be much more confusing.

  12. 298 remaining items

  13. MehrdadKhnzd commented on Jul 8, 2019

    @MehrdadKhnzd

    Hello there! When we have to expect the stable release of this feature? And how can I use that in nightly builds?

  14. MajorBreakfast commented on Jul 8, 2019

    @MajorBreakfast
    Contributor

    @MehrdadKhnzd #62149 contains information about the target release date and more

  15. DoumanAsh commented on Aug 11, 2019

    @DoumanAsh

    Is there plan to automatically implement Unpin for futures that are generated by async fn?

    Specifically I'm wondering if Unpin is not available automatically due to generated Future code itself, or whether cuz we can use references as arguments

  16. jkarneges commented on Aug 17, 2019

    @jkarneges
    Contributor

    @DoumanAsh I suppose if an async fn never has any active self-references at yield points then the generated Future could conceivably implement Unpin, maybe?

  17. jonhoo commented on Aug 20, 2019

    @jonhoo
    Contributor

    I think that would need to be accompanied by some pretty helpful error messages saying "not Unpin because of this borrow" + a hint of "alternatively you can box this future"

  18. added a commit that references this issue on Aug 20, 2019
  19. bstrie commented on Sep 23, 2019

    @bstrie
    Contributor

    The stabilization PR at #63209 notes that "All the blockers are now closed." and was landed into nightly on August 20, therefore heading for the beta cut later this week. It seems worth noting that since August 20 some new blocking issues have been filed (as tracked by the AsyncAwait-Blocking tag). Two of these (#63710, #64130) appear to be nice-to-haves that would not actually impede stabilization, however there are three other issues (#64391, #64433, #64477) which seem worth discussing. These latter three issues are related, all of them arising due to PR #64292, which itself was landed to address AsyncAwait-Blocking issue #63832. A PR, #64584, has already landed in an attempt to address the bulk of the problems, but the three issues remain open for now.

    The silver lining is that the three serious open blockers appear to concern code that should compile, but does not currently compile. In that sense, it would be backwards-compatible to land fixes later without impeding the beta-ization and eventual stabilization of async/await. However, I'm wondering whether anyone from the lang team thinks that anything here is concerning enough to suggest that async/await should bake on nightly for another cycle (which, as distasteful as that may sound, is the point of the rapid release schedule after all).

  20. Centril commented on Sep 23, 2019

    @Centril
    Contributor

    @bstrie We are just reusing "AsyncAwait-Blocking" for lack of a better label to note them as "high priority", they are not actually blocking. We should revamp the labeling system soon to make it less confusing, cc @nikomatsakis.

  21. earthengine commented on Sep 29, 2019

    @earthengine
  22. jonhoo commented on Sep 29, 2019

    @jonhoo
    Contributor

    @earthengine I don't think that's a fair assessment of the situation. The issues that have come up have all been worth taking seriously. It wouldn't be good to land async await only for people to then run into these issues trying to use it in practice :)

  23. added a commit that references this issue on Feb 24, 2025
  24. added a commit that references this issue on Feb 24, 2025
  25. added 2 commits that reference this issue on Mar 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-async-awaitArea: Async & AwaitA-coroutinesArea: CoroutinesAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.B-RFC-approvedBlocker: Approved by a merged RFC but not yet implemented.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCT-langRelevant to the language team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions