1. 75
  1.  

  2. 7

    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.

    1. 15

      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.

      1. 15

        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.

        1. 5

          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.

          1. 1

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

            1. 1

              Async is the easier part. async fn will be sugar:

              async fn async_fun() -> String {
                // something
              }
              
              fn async_fun() -> impl Future<Item=String> {
                // something
              }
              

              In the back, this builds a Generator. await is setting up the yield points of the generator.

              async fn async_fun() -> String {
                let future = futures::future::ok(String::from("hello, i'm not really asynchronous, but i need quick example!"));
                let string: String = await!(future);
                string
              }
              

              So yes, the type mismatch would occur at the binding of the await and the right hand side is much easier to grasp. Basically, “and_then” for chaining can now largely be replaced by “await”.

          2. 1

            Ah, you’re right. I SHOULD know this in fact from the bad old days of Java when “Non Blocking IO” came out :)

            1. 1

              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.

            2. 3

              This sums it up very well. I can do everything I personally want to do with Futures as they exist now in Rust. That said, I feel like async/await will really clean things up when they land.

              1. 2

                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.

              2. 9

                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:

                1. 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.
                2. 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).
                3. 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.
                4. “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.

                There’s a cool and very in-depth series of articles about the difficulty of implementing async/await in rust starting here: https://boats.gitlab.io/blog/post/2018-01-25-async-i-self-referential-structs/ (I’m pretty sure this was posted to lobsters before, but search is still broken so I can’t find it.)

                1. 8

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

                  Going further: it follows the very general algebraic pattern of monad. Haskell has “do-notation” syntax which works for Promises but also Maybe, Either, Parser, etc.

                2. 8

                  In addition to the great explanations of others, here are a couple diffs where the Fuchsia team at Google was able to really clean up some code by switching to async/await:

                  1. 1

                    Interesting! That speaks to the Rust 2018 initiative’s focus on ‘embedded’ in the mobile sense.

                    1. 3

                      The initiative has been surprisingly successful. Most of my clients are currently on embedded Linux and smaller.

                3. 2

                  Good use for release tag!