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

Open
withoutboats opened this Issue May 8, 2018 · 198 comments

Comments

Projects
None yet
@withoutboats
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:

  • Implement
  • Stabilize
  • Document

Unresolved questions:

@rpjohnst

This comment has been minimized.

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

This comment has been minimized.

Contributor

withoutboats commented May 9, 2018

Implementation is blocked on #50307.

@berkus berkus referenced this issue May 10, 2018

Open

Futures v0.2 #98

@Pzixel

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

rpjohnst commented May 10, 2018

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

rpjohnst commented May 10, 2018

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

rpjohnst commented May 11, 2018

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

rpjohnst commented May 11, 2018

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

This comment has been minimized.

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.

@MajorBreakfast

This comment has been minimized.

Contributor

MajorBreakfast commented Aug 2, 2018

@Centril I agree. It should be usable everywhere. I'm just not sure anymore whether -> is really the right choice. I associate -> with being callable. And async blocks aren't.

@Centril

This comment has been minimized.

Contributor

Centril commented Aug 2, 2018

@MajorBreakfast I basically agree; I think we should use : for type ascription, so async : Type { .. }, try : Type { .. }, and expr : Type. We've discussed the potential ambiguities on Discord and I think we found a way forward with : that makes sense...

@Pzixel

This comment has been minimized.

Pzixel commented Aug 2, 2018

Another question is about Either enum. We already have Either in futures crate. It's also is confusing because it looks just like Either from either crate when it's not.

Because Futures seems to be merged in std (at least very basic parts of it) could we also include Either there? It's crucial to have them in order to be able to return impl Future from the function.

For example, I often write code like following:

fn handler() -> impl Future<Item = (), Error = Bar> + Send {
	someFuture()
		.and_then(|x| {
			if condition(&x) {
				Either::A(anotherFuture(x))
			} else {
				Either::B(future::ok(()))
			}
		})
}

I'd like to be able to write it like:

async fn handler() -> Result<(), Bar> {
	let x = await someFuture();
	if condition(&x) {
		await anotherFuture(x);
	}
}

But as I understand when async gets expanded it requires Either to be inserted here, because we either go into condition or we don't.

You can find actual code here if you wish. You can see that it has lots of Eithers and they all seems to exist in expanded code

@Nemo157

This comment has been minimized.

Contributor

Nemo157 commented Aug 2, 2018

@Pzixel you won't need Either inside async functions, as long as you await the futures then the code transformation that async does will hide those two types internally and present a single return type to the compiler.

@Ekleog

This comment has been minimized.

Ekleog commented Aug 2, 2018

@Pzixel Also, I (personally) hope Either is not going to be introduced with this RFC, because that'd be introducing a restricted version of rust-lang/rfcs#2414 (that works only with 2 types and only with Futures), thus likely adding API cruft if a general solution is ever merged -- and as @Nemo157 mentioned it doesn't seem to be an emergency to have Either right now :)

@Pzixel

This comment has been minimized.

Pzixel commented Aug 2, 2018

@Ekleog sure, I just was hitted by this idea that I actually have tons of either's in my existing async code and I'd really like to get rid of them. Then I recalled my confusion when I spent ~half of an hour until I realized that it doesn't compile because I had either crate in dependencies (future errors are quite hard to understand so it took quite a long). So this is why I have written the comment, just to be sure this problem is addressed somehow.

Of course, this is not related to async/await only, it's more generic thing, so it deserves its own RFC. I only wanted to emphasize that either futures should know about either or vice versa (in order to implement IntoFuture correctly).

@MajorBreakfast

This comment has been minimized.

Contributor

MajorBreakfast commented Aug 2, 2018

@Pzixel The Either exported by the futures crate is a reexport from the either crate. The futures crate 0.3 can't implement Future for Either because of the orphan rules. It's highly likely that we're going to also remove the Stream and Sink impls for Either for consistency and offer an alternative instead (dicussed here). Additionally, the either crate could then implement Future, Stream and Sink itself, likely under a feature flag.

That said, as @Nemo157 already mentioned, when working with futures, it's better to just use async functions instead of Either.

@Centril

This comment has been minimized.

Contributor

Centril commented Aug 10, 2018

The async : Type { .. } stuff is now proposed in rust-lang/rfcs#2522.

@Ekleog

This comment has been minimized.

Ekleog commented Aug 10, 2018

Are async/await functions automatically implementing Send already implemented?

It looks like the following async function is not (yet?) Send:

pub async fn __receive() -> ()
{
    let mut chan: futures::channel::mpsc::Receiver<Box<Send + 'static>> = None.unwrap();

    await!(chan.next());
}

Link to full reproducer (that doesn't compile on the playground for lack of futures-0.3, I guess) is here.

@Ekleog

This comment has been minimized.

Ekleog commented Aug 10, 2018

Also, when investigating this issue I came upon #53249, which I guess should be added to the tracking list of the topmost post :)

@Nemo157

This comment has been minimized.

Contributor

Nemo157 commented Aug 10, 2018

Here's a playground showing that async/await functions implementing Send should work. Uncommenting the Rc version correctly detects that function as non-Send. I can take a look at your specific example in a bit (no Rust compiler on this machine 🙁) to try and work out why it's not working.

@cramertj

This comment has been minimized.

Member

cramertj commented Aug 10, 2018

@Ekleog std::mpsc::Receiver isn't Sync, and the async fn you wrote holds a reference to it. References to !Sync items are !Send.

@Ekleog

This comment has been minimized.

Ekleog commented Aug 10, 2018

@cramertj Hmm… but, am I not holding an owned mpsc::Receiver, which should be Send iff its generic type is Send? (also, it's not a std::mpsc::Receiver but a futures::channel::mpsc::Receiver, which is Sync too if the type is Send, sorry for not noticing the mpsc::Receiver alias was ambiguous!)

@Nemo157 Thanks! I've opened #53259 in order to avoid too much noise on this issue :)

@rpjohnst

This comment has been minimized.

Contributor

rpjohnst commented Aug 13, 2018

The question of whether and how async blocks allow ? and other control flow might warrant some interaction with try blocks (e.g. try async { .. } to allow ? without similar confusion to return?).

This means the mechanism for specifying an async block's type may need to interact with the mechanism for specifying a try block's type. I left a comment on the ascription syntax RFC: rust-lang/rfcs#2522 (comment)

@Ekleog

This comment has been minimized.

Ekleog commented Aug 15, 2018

Just hit what at first I thought was a futures-rs issue, but it turns out it may actually be an async/await issue, so here it is: rust-lang-nursery/futures-rs#1199 (comment)

@Ekleog

This comment has been minimized.

Ekleog commented Aug 17, 2018

One new issue: #53447

@Nemo157

This comment has been minimized.

Contributor

Nemo157 commented Aug 28, 2018

As discussed a few days ago on discord, await has not yet been reserved as a keyword. It is pretty critical to get this reservation in (and added to the 2018 edition keyword lint) before the 2018 release. It’s a slightly complicated reservation since we want to continue using the macro syntax for now.

@panicbit

This comment has been minimized.

Contributor

panicbit commented Sep 4, 2018

Will the Future/Task API have a way to spawn local futures?
I see that there's SpawnLocalObjError, but it seems to be unused.

@MajorBreakfast

This comment has been minimized.

Contributor

MajorBreakfast commented Sep 4, 2018

@panicbit At the working group we're currently discussing whether it makes sense to include spawning functionality in the context at all. rust-lang-nursery/wg-net#56

(SpawnLocalObjError is not entirely unused: LocalPool of the futures crate uses it. You're correct, however, that nothing in libcore uses it)

@Nashenas88

This comment has been minimized.

Contributor

Nashenas88 commented Nov 3, 2018

@withoutboats I noticed a few of the links in the issue description are out of date. Specifically, rust-lang/rfcs#2418 is closed and rust-lang-nursery/futures-rs#1199 has been moved to #53548

@jethrogb

This comment has been minimized.

Contributor

jethrogb commented Nov 11, 2018

NB. The name of this tracking issue is async/await but it is assigned to the task API as well! The task API currently has a stabilization RFC pending: rust-lang/rfcs#2592

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment