Can someone explain to me, a Rust tourist at best, why async/await are desirable in Rust when awesome sauce concurrency because of the ownership / borrowing model have been baked into Rust since its inception?

FWIW I also really like the idea of working groups, and I think focusing on the areas where Rust gets the widest usage is super smart.

The current Futures implementation exerts a lot of “rightward pressure” when you’re trying to chain multiple future results together. It works, and works safely, but it’s a bit messy to work with and there’s a lot of nesting to deal with, which isn’t easily readable.

The async/await proposal is basically syntactic sugar to linearize logic like that into a straight-line set of reasoning that’s a lot easier to work with.

The biggest problem with the current Futures, as far as my experience goes, is that the method-chaining style involves so much type inference that if you screw up a type somewhere the compiler has no prayer of figuring out what you meant it to be, or even really where the problem is. So you have to keep everything in your head in long chains of futures. I’m expecting async/await to help with this just by actually breaking the chains down to individual expressions that can be type-checked individually.

Edit: And it’s desirable in Rust because async I/O is almost always(?) going to be faster than blocking I/O, no matter whether it’s single threaded or multi-threaded. So it doesn’t necessarily have anything to do with threads, but rather is an orthogonal axis in the same problem space.

I hope a lot of care is taken to make it easy to specify intermediate type signatures. I know that in other languages with type inference I’ll “assert” a signature halfway through some longer code mainly as docs but also to bisect type error issues.

Totally agreed. As far as I understand (which is not much), saying async foo(); is similar to return foo(); in how the language treats it, so you should be able to get the compiler pointing quite specifically at that one line as the place the type mismatch occurs and what it is. If you have to do foo().and_then(bar).and_then(bop); then it just says “something went wrong in this expression, sorry, here’s the ten-generic-deep nested combinator that has an error somewhere”.

This has pretty much been my only major negative with rust up to this point, i’ve got three apps underway in rust all using Futures and it just starts getting hairy when you get to a certain level of complexity, to the point you can be hammering out code and when you get to your Futures chaining it stops you dead in your tracks because it’s hard to read and hard to reason about quickly. So i’m on board with async/await reserves for sure.

That’s interesting! I guess I’d mostly thought of async/await as coming into play in languages like Python or Javascript where real concurrency wasn’t possible, but I suppose using them as a conceptual interface like this with real concurrency underneath makes a lot of sense too.

I believe async/await are desirable in all languages that implement async I/O because the languages usually walk this path, motivated by code ergonomics:

Async I/O and timing functions return immediately, and accept a function to call (“callback”) when they’re done. Code becomes a pile of deeply nested callbacks, resulting in the “ziggurat” or “callback hell” look.

Futures (promises) are introduced to wrap the callback and success/failure result into an object that can be manipulated. Nested callbacks become chained calls to map/flatMap (sometimes called then).

Generators/coroutines are introduced to allow a function to suspend itself when it’s waiting for more data. An event loop (“executor”, “engine”) allows generators to pause each time a future is pending, and resume when it’s ready.

“async”/“await” keywords are added to simplify wiring up a promise-based generator.

In rust’s case, I think it was “implement generators/coroutines” which hit snags with the existing borrow checker.