Skip to content
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 async/await (RFC 2394) #50547

Closed
10 of 11 tasks
withoutboats opened this issue May 8, 2018 · 308 comments · Fixed by #63209
Closed
10 of 11 tasks

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

withoutboats opened this issue May 8, 2018 · 308 comments · Fixed by #63209
Labels
A-async-await Area: Async & Await A-coroutines Area: Coroutines AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. B-RFC-approved Feature: Approved by a merged RFC but not yet implemented. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@withoutboats
Copy link
Contributor

withoutboats commented May 8, 2018

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:

@withoutboats withoutboats added B-RFC-approved Feature: Approved by a merged RFC but not yet implemented. T-lang Relevant to the language team, which will review and decide on the PR/issue. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. A-coroutines Area: Coroutines E-needs-mentor labels May 8, 2018
@rpjohnst
Copy link
Contributor

rpjohnst commented May 8, 2018

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

@withoutboats
Copy link
Contributor Author

Implementation is blocked on #50307.

@Pzixel
Copy link

Pzixel commented May 10, 2018

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.

@alexreg
Copy link
Contributor

alexreg commented May 10, 2018

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.

@Nemo157
Copy link
Member

Nemo157 commented May 10, 2018

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()?)?;

@rpjohnst
Copy link
Contributor

rpjohnst commented May 10, 2018

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.

@alexreg
Copy link
Contributor

alexreg commented May 10, 2018

@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...

@Pzixel
Copy link

Pzixel commented May 10, 2018

@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.

@rpjohnst
Copy link
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()?

@Pzixel
Copy link

Pzixel commented May 10, 2018

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.

@alexreg
Copy link
Contributor

alexreg commented May 10, 2018

@Pzixel Yeah, the second option sounds like one of the more preferable ones. I've used async/await in C# too, but not very much, since I haven't programmed principally in C# for some years now. As for precedence, await (future?) is more natural to me.

@rpjohnst I kind of like the idea of a postfix operator, but I'm also worried about readability and assumptions people will make – it could easily get confused for a member of a struct named await.

@rpjohnst
Copy link
Contributor

rpjohnst commented May 10, 2018

Possibility to distinguish task start and task await is really important.

For what it's worth, the implicit version does do this. It was discussed to death both in the RFC thread and in the internals thread, so I won't go into a lot of detail here, but the basic idea is only that it moves the explicitness from the task await to task construction- it doesn't introduce any new implicitness.

Your example would look something like this:

pub async fn get_statuses() -> Vec<StatusUpdate> {
    // get_status_updates is also an `async fn`, but calling it works just like any other call:
    let count = get_status_updates();

    let mut tasks = vec![];
    for i in 0..count {
        // Here is where task *construction* becomes explicit, as an async block:
        task.push(async {
            // Again, simply *calling* get_status_update looks just like a sync call:
            let status_update: StatusUpdateStruct = get_status_update(i).deserialize();
            StatusUpdate::new(utc_from_ticks(status_update.update_date), status_update.status_code, status_update.note))
        });
    }

    // And finally, launching the explicitly-constructed futures is also explicit, while awaiting the result is implicit:
    join_all(&tasks[..])
}

This is what I meant by "for this to work, the act of constructing a future must also be made explicit." It's very similar to working with threads in sync code- calling a function always waits for it to complete before resuming the caller, and there are separate tools for introducing concurrency. For example, closures and thread::spawn/join correspond to async blocks and join_all/select/etc.

@Pzixel
Copy link

Pzixel commented May 10, 2018

For what it's worth, the implicit version does do this. It was discussed to death both in the RFC thread and in the internals thread, so I won't go into a lot of detail here, but the basic idea is only that it moves the explicitness from the task await to task construction- it doesn't introduce any new implicitness.

I believe it does. I can't see here what flow would be in this function, where is points where execution breaks until await is completed. I only see async block which says "hello, somewhere here there are async functions, try to find out which ones, you will be surprised!".

Another point: Rust tend to be a language where you can express everything, close to bare metal and so on. I'd like to provide some quite artificial code, but I think it illustrates the idea:

var a = await fooAsync(); // awaiting first task
var b = barAsync(); //running second task
var c = await bazAsync(); // awaiting third task
if (c.IsSomeCondition && !b.Status = TaskStatus.RanToCompletion) // if some condition is true and b is still running
{
   var firstFinishedTask = await Task.Any(b, Task.Delay(5000)); // waiting for 5 more seconds;
   if (firstFinishedTask != b) // our task is timeouted
      throw new Exception(); // doing something
   // more logic here
}
else
{
   // more logic here
}

Rust always tends to provide full control over what's happening. await allow you to specify points where continuation process. It also allows you to unwrap a value inside future. If you allows implicit conversion on use side, it has several implications:

  1. First of all, you have to write some dirty code to just emulate this behaviour.
  2. Now RLS and IDEs should expect that our value is either Future<T> or awaited T itself. It's not an issue with keywords - it it exists, then result is T, otherwise it's Future<T>
  3. It makes code harder to understand. In you example I don't see why it does interrupt execution at get_status_updates line, but it doesn't on get_status_update. They are quite similar to each other. So it's either doesn't work the way original code was or it's so much complicated that I can't see it even when I'm quite familiar with the subject. Both alternatives don't make this option a favor.

@rpjohnst
Copy link
Contributor

I can't see here what flow would be in this function, where is points where execution breaks until await is completed.

Yes, this is what I meant by "this makes suspension points harder to see." If you read the linked internals thread, I made an argument for why this isn't that big of a problem. You don't have to write any new code, you just put the annotations in a different place (async blocks instead of awaited expressions). IDEs have no problem telling what the type is (it's always T for function calls and Future<Output=T> for async blocks).

I will also note that your understanding is probably wrong regardless of the syntax. Rust's async functions do not run any code at all until they are awaited in some way, so your b.Status != TaskStatus.RanToCompletion check will always pass. This was also discussed to death in the RFC thread, if you're interested in why it works this way.

In you example I don't see why it does interrupt execution at get_status_updates line, but it doesn't on get_status_update. They are quite similar to each other.

It does interrupt execution in both places. The key is that async blocks don't run until they are awaited, because this is true of all futures in Rust, as I described above. In my example, get_statuses calls (and thus awaits) get_status_updates, then in the loop it constructs (but does not await) count futures, then it calls (and thus awaits) join_all, at which point those futures concurrently call (and thus await) get_status_update.

The only difference with your example is when exactly the futures start running- in yours, it's during the loop; in mine, it's during join_all. But this is a fundamental part of how Rust futures work, not anything to do with the implicit syntax or even with async/await at all.

@Pzixel
Copy link

Pzixel commented May 10, 2018

I will also note that your understanding is probably wrong regardless of the syntax. Rust's async functions do not run any code at all until they are awaited in some way, so your b.Status != TaskStatus.RanToCompletion check will always pass.

Yes, C# tasks are executed synchronously until first suspension point. Thank you for pointing that out.
However, it doesn't really matter because I still should be able to run some task in background while executing the rest of the method and then check if background task is finished. E.g. it could be

var a = await fooAsync(); // awaiting first task
var b = Task.Run(() => barAsync()); //running background task somehow
// the rest of the method is the same

I've got your idea about async blocks and as I see they are the same beast, but with more disadvantages. In original proposal each async task is paired with await. With async blocks each task would be paired with async block at construction point, so we are in almost same situation as before (1:1 relationship), but even a bit worse, because it feel more unnatural, and harder to understand, because callsite behavior becomes context-depended. With await I can see let a = foo() or let b = await foo() and I would know it this task is just constructed or constructed and awaited. If i see let a = foo() with async blocks I have to look if there is some async above, if I get you right, because in this case

pub async fn get_statuses() -> Vec<StatusUpdate> {
    // get_status_updates is also an `async fn`, but calling it works just like any other call:
    let count = get_status_updates();

    let mut tasks = vec![];
    for i in 0..count {
        // Here is where task *construction* becomes explicit, as an async block:
        task.push(async {
            // Again, simply *calling* get_status_update looks just like a sync call:
            let status_update: StatusUpdateStruct = get_status_update(i).deserialize();
            StatusUpdate::new(utc_from_ticks(status_update.update_date), status_update.status_code, status_update.note))
        });
    }

    // And finally, launching the explicitly-constructed futures is also explicit, while awaiting the result is implicit:
    join_all(&tasks[..])
}

We are awaiting for all tasks at once while here

pub async fn get_statuses() -> Vec<StatusUpdate> {
    // get_status_updates is also an `async fn`, but calling it works just like any other call:
    let count = get_status_updates();

    let mut tasks = vec![];
    for i in 0..count {
        // Isn't "just a construction" anymore
        task.push({
            let status_update: StatusUpdateStruct = get_status_update(i).deserialize();
            StatusUpdate::new(utc_from_ticks(status_update.update_date), status_update.status_code, status_update.note))
        });
    }
    tasks 
}

We are executing them one be one.

Thus I can't say what's exact behavior of this part:

let status_update: StatusUpdateStruct = get_status_update(i).deserialize();
StatusUpdate::new(utc_from_ticks(status_update.update_date), status_update.status_code, status_update.note))

Without having more context.

And things get more weird with nested blocks. Not to mention questions about tooling etc.

@rpjohnst
Copy link
Contributor

callsite behavior becomes context-depended

This is already true with normal sync code and closures. For example:

// Construct a closure, delaying `do_something_synchronous()`:
task.push(|| {
    let data = do_something_synchronous();
    StatusUpdate { data }
});

vs

// Execute a block, immediately running `do_something_synchronous()`:
task.push({
    let data = do_something_synchronous();
    StatusUpdate { data }
});

One other thing that you should note from the full implicit await proposal is that you can't call async fns from non-async contexts. This means that the function call syntax some_function(arg1, arg2, etc) always runs some_function's body to completion before the caller continues, regardless of whether some_function is async. So entry into an async context is always marked explicitly, and function call syntax is actually more consistent.

@jplatte
Copy link
Contributor

jplatte commented May 11, 2018

Regarding await syntax: What about a macro with method syntax? I can't find an actual RFC for allowing this, but I've found a few discussions (1, 2) on reddit so the idea is not unprecedented. This would allow await to work in postfix position without making it a keyword / introducing new syntax for only this feature.

// Postfix await-as-a-keyword. Looks as if we were accessing a Result<_, _> field,
// unless await is syntax-highlighted
first().await?.second().await?.third().await?
// Macro with method syntax. A few more symbols, but clearly a macro invocation that
// can affect control flow
first().await!()?.second().await!()?.third().await!()?

@fdietze
Copy link

fdietze commented May 11, 2018

There is a library from the Scala-world which simplifies monad compositions: http://monadless.io

Maybe some ideas are interesting for Rust.

quote from the docs:

Most mainstream languages have support for asynchronous programming using the async/await idiom or are implementing it (e.g. F#, C#/VB, Javascript, Python, Swift). Although useful, async/await is usually tied to a particular monad that represents asynchronous computations (Task, Future, etc.).

This library implements a solution similar to async/await but generalized to any monad type. This generalization is a major factor considering that some codebases use other monads like Task in addition to Future for asynchronous computations.

Given a monad M, the generalization uses the concept of lifting regular values to a monad (T => M[T]) and unlifting values from a monad instance (M[T] => T). > Example usage:

lift {
  val a = unlift(callServiceA())
  val b = unlift(callServiceB(a))
  val c = unlift(callServiceC(b))
  (a, c)
}

Note that lift corresponds to async and unlift to await.

@Pzixel
Copy link

Pzixel commented May 11, 2018

This is already true with normal sync code and closures. For example:

I see several differences here:

  1. Lambda context is unavoidable, but it's not for await. With await we don't have a context, with async we have to have one. The former wins, because it provide the same features, but require knowing less about the code.
  2. Lambdas tends to be short, several lines at most so we see the entire body at once, and simple. async functions may be quite big (as big, as regular functions) and complicated.
  3. Lambdas are rarely nested (except for then calls, but it's await is proposed for), async blocks are nested frequently.

One other thing that you should note from the full implicit await proposal is that you can't call async fns from non-async contexts.

Hmm, I didn't notice that. It doesn't sound good, because in my practice you often want to run async from non-async context. In C# async is just a keyword that allows compiler to rewrite function body, it doesn't affect function interface in any way so async Task<Foo> and Task<Foo> are completely interchangeable, and it decouples implementation and API.

Sometimes you may want to to block on async task, e.g when you want to call some network API from main. You have to block (otherwise you return to the OS and the program ends) but you have to run async HTTP request. I'm not sure what solution could be here except hacking main to allow it to be async as well as we do with Result main return type, if you cannot call it from non-async main.

Another consideration in favor of current await is how it works in other popular language (as noted by @fdietze ). It makes it easier to migrate from other language such as C#/TypeScript/JS/Python and thus is a better approach in terms of drumming up new people.

@rpjohnst
Copy link
Contributor

rpjohnst commented May 11, 2018

I see several differences here

You should also realize that the main RFC already has async blocks, with the same semantics as the implicit version, then.

It doesn't sound good, because in my practice you often want to run async from non-async context.

This is not an issue. You can still use async blocks in non-async contexts (which is fine because they just evaluate to a F: Future as always), and you can still spawn or block on futures using exactly the same API as before.

You just can't call async fns, but instead wrap the call to them in an async block- as you do regardless of the context you're in, if you want a F: Future out of it.

async is just a keyword that allows compiler to rewrite function body, it doesn't affect function interface in any way

Yes, this is is a legitimate difference between the proposals. It was also covered in the internals thread. Arguably, having different interfaces for the two is useful because it shows you that the async fn version will not run any code as part of construction, while the -> impl Future version may e.g. initiate a request before giving you a F: Future. It also makes async fns more consistent with normal fns, in that calling something declared as -> T will always give you a T, regardless of whether it's async.

(You should also note that in Rust there is still quite a leap between async fn and the Future-returning version, as described in the RFC. The async fn version does not mention Future anywhere in its signature; and the manual version requires impl Trait, which carries with it some problems to do with lifetimes. This is, in fact, part of the motivation for async fn to begin with.)

It makes it easier to migrate from other language such as C#/TypeScript/JS/Python

This is an advantage only for the literal await future syntax, which is fairly problematic on its own in Rust. Anything else we might end up with also has a mismatch with those languages, while implicit await at least has a) similarities with Kotlin and b) similarities with synchronous, thread-based code.

@Pzixel
Copy link

Pzixel commented May 11, 2018

Yes, this is is a legitimate difference between the proposals. It was also covered in the internals thread. Arguably, having different interfaces for the two is useful

I'd say having different interfaces for the two has some advantages, because having API depended on implementation detail doesn't sound good to me. For example, you are writing a contract that is simply delegating a call to internal future

fn foo(&self) -> Future<T> {
   self.myService.foo()
}

And then you just want to add some logging

async fn foo(&self) -> T {
   let result = await self.myService.foo();
   self.logger.log("foo executed with result {}.", result);
   result
}

And it becomes a breaking change. Whoa?

This is an advantage only for the literal await future syntax, which is fairly problematic on its own in Rust. Anything else we might end up with also has a mismatch with those languages, while implicit await at least has a) similarities with Kotlin and b) similarities with synchronous, thread-based code.

It's an advantage for any await syntax, await foo/foo await/foo@/foo.await/... once you get that it's the same thing, the only difference is that you place it before/after or have a sigil instead of keyword.

You should also note that in Rust there is still quite a leap between async fn and the Future-returning version, as described in the RFC

I know it and it disquiets me a lot.

@rpjohnst
Copy link
Contributor

And it becomes a breaking change.

You can get around that by returning an async block. Under the implicit await proposal, your example looks like this:

fn foo(&self) -> impl Future<Output = T> { // Note: you never could return `Future<T>`...
    async { self.my_service.foo() } // ...and under the proposal you couldn't call `foo` outside of `async` either.
}

And with logging:

fn foo(&self) -> impl Future<Output = T> {
    async {
        let result = self.my_service.foo();
        self.logger.log("foo executed with result {}.", result);
        result
    }
}

The bigger issue with having this distinction arises during the transition of the ecosystem from manual future implementations and combinators (the only way today) to async/await. But even then the proposal allows you to keep the old interface around and provide a new async one alongside it. C# is full of that pattern, for example.

@Pzixel
Copy link

Pzixel commented May 12, 2018

Well, that sounds reasonable.

However, I do believe such implicitness (we don't see if foo() here is async or sync function) lead to the same problems that arised in protocols such as COM+ and was a reason for WCF being implemented as it was. People had problems when async remote requests were looking like simple methods calls.

This code looks perfectly fine except I can't see if some request if async or sync. I believe that it's important information. For example:

fn foo(&self) -> impl Future<Output = T> {
    async {
        let result = self.my_service.foo();
        self.logger.log("foo executed with result {}.", result);
        let bars: Vec<Bar> = Vec::new();
        for i in 0..100 {
           bars.push(self.my_other_service.bar(i, result));
        }
        result
    }
}

It's crucial to know if bar is sync or async function. I often see await in the loop as a marker that this code have to be changed to achieve better throughout load and performance. This is a code I reviewed yesterday (code is suboptimal, but it's one of review iterations):

image

As you can see, I easily spotted that we have a looping await here and I asked to change it. When change was committed we got 3x page load speedup. Without await I could easily overlook this misbehaviour.

@rust-lang rust-lang deleted a comment from chpio May 9, 2019
@elpiel
Copy link

elpiel commented Jun 12, 2019

@withoutboats to my understanding the final syntax is agreed upon already, maybe it's time to mark it as Done? 😊

@bstrie
Copy link
Contributor

bstrie commented Jun 14, 2019

Is the intent to stabilize in time for the next beta cut on July 4, or will blocking bugs require another cycle to resolve? There are plenty of open issues under the A-async-await tag, though I'm not sure how many of those are critical.

@bstrie
Copy link
Contributor

bstrie commented Jun 14, 2019

Aha, disregard that, I've just discovered the AsyncAwait-Blocking label.

@MehrdadKhnzd
Copy link

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

@MajorBreakfast
Copy link
Contributor

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

@DoumanAsh
Copy link

DoumanAsh commented Aug 11, 2019

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

@jkarneges
Copy link
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?

@jonhoo
Copy link
Contributor

jonhoo commented Aug 20, 2019

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"

@bors bors closed this as completed in 3f7e7c2 Aug 20, 2019
@bstrie
Copy link
Contributor

bstrie commented Sep 23, 2019

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).

@Centril
Copy link
Contributor

Centril commented Sep 23, 2019

@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.

@earthengine

This comment has been minimized.

@jonhoo
Copy link
Contributor

jonhoo commented Sep 29, 2019

@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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await Area: Async & Await A-coroutines Area: Coroutines AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. B-RFC-approved Feature: Approved by a merged RFC but not yet implemented. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.