[HN Gopher] Six ways to make async Rust easier
       ___________________________________________________________________
        
       Six ways to make async Rust easier
        
       Author : gbrown_
       Score  : 50 points
       Date   : 2021-06-17 18:03 UTC (4 hours ago)
        
 (HTM) web link (carllerche.com)
 (TXT) w3m dump (carllerche.com)
        
       | akiselev wrote:
       | That comic at the end perfectly sums up my reaction (mostly
       | because I want GATs though :)).
       | 
       | I'm not fully sold on Rust's existing async/await implementation
       | but how would this suggestion even be implemented? It's a massive
       | breaking change on multiple fronts and I don't know how a new
       | edition would paper over the impedance mismatch between the new
       | system and async libraries written in the older edition. Feels
       | like I need another year or two of using async rust in production
       | to even begin to form an opinion on this.
       | 
       | How would this work on embedded? I haven't done embedded since
       | long before I could use async/await or Rust but it sounds like
       | implicit _anything_ (except maybe autoref /deref) is a deal
       | breaker.
        
         | carllerche wrote:
         | I believe it would be possible to implement using an edition in
         | a way that any *old* code is compatible with new code.
         | 
         | Regarding implicitness, a lot in Rust already is implicit (e.g.
         | type inference). Good implicitness only feels weird at first,
         | before we get used to it. I linked to Aaron Turon's article on
         | reasoning footprint, but as a follow up, there is withoutboat's
         | article on "not explicit" that goes into the topic more:
         | https://boats.gitlab.io/blog/post/2017-12-27-things-explicit...
        
       | staticassertion wrote:
       | It feels like the fundamental issue here is shared mutable state,
       | and much of the solution is to just not have that.
       | 
       | I don't believe this requires a change to async-await though. It
       | just means you want to wrap up your async-await in its own
       | synchronization primitive, or just avoid the need for it.
       | 
       | This is easy to do with a channel system, or an actor system,
       | where the queue is the synchronizing primitive. An actor's
       | message handler can internally be asynchronous, so long as it
       | processes messages one at a time (but it can yield during
       | processing to another actor).
       | 
       | We can get actors in rust pretty trivially imo. It's just a task
       | with state and a message interface.
        
       | layoutIfNeeded wrote:
       | These personas (i.e. "Alan") are annoying enough when used by
       | business people, but in the context of a programming language
       | it's beyond cringe.
        
       | littlestymaar wrote:
       | > I believe that the better reason for asynchronous is it enables
       | modeling complex flow control efficiently. For example, patterns
       | like pausing or canceling an in-flight operation are challenging
       | without asynchronous programming.
       | 
       | This is exactly why I think _async /await_ is a must have for any
       | language aimed at back-end. Nodejs has a lot of pitfalls (promise
       | cancellation being a major one), but moving from it to Go felt
       | like huge step back to me: having to use channels to synchronize
       | your tasks feels really cumbersome when you're used to
       | promises/Future with combinators and _async /await_.
        
         | convolvatron wrote:
         | I find continuations alot easier to reason about as a
         | programmer. I think async/await was chosen here because in
         | simple cases it pisses off the borrower less
        
         | cube2222 wrote:
         | I obviously can't see your code, but in my experience, working
         | with Go daily for years, explicit channels are an extremely
         | rare occurrence.
         | 
         | If you want to run multiple tasks and wait for them to finish,
         | you use a WaitGroup. If you want to stop processing on first
         | error encountered, you use an ErrGroup. And cancellation with
         | contexts also works very well overall.
         | 
         | I actually think Go does it way better than all the "visible
         | async/await" languages. Because usually when you write code,
         | you just want to await the result, so in Go, awaiting is the
         | default. In order to run a task without awaiting immediately,
         | you use the 'go' keyword (at least that's the semantics of it
         | all). It also omits the function coloring problem (this is a
         | very big advantage), because all functions are async.
         | 
         | For my use cases channels are usually only useful in two
         | situations:
         | 
         | 1. Asychronous producers and consumers.
         | 
         | 2. Creating an "actor" which receives messages on a channel/s
         | and processes them in a serialized manner. Where the messages
         | come from multiple event sources.
         | 
         | As an aside, I do understand why performance oriented languages
         | like Rust choose the async/await path, as async by default
         | carries a performance penalty / requires a runtime. I don't
         | however accept it in non-performance oriented languages.
        
           | akiselev wrote:
           | _> As an aside, I do understand why performance oriented
           | languages like Rust choose the async /await path, as async by
           | default carries a performance penalty / requires a runtime. I
           | don't however accept it in non-performance oriented
           | languages._
           | 
           | Ironically, I think Rust futures could be expanded into Go-
           | like uncolored functions relatively easily without even much
           | of a runtime. Since they're stack allocated by default,
           | driving a future to completion is a matter of calling
           | Future::poll in a while loop and handling the waker callback.
           | I feel like the runtimes are there mostly to provide a
           | unified interface to kernel APIs like io_uring/epoll/etc and
           | ease ownership/task allocation.
        
           | Animats wrote:
           | Go really does do this better. Go is a green thread system.
           | 
           | In Rust, if you're actually doing any compute work, you're
           | stalling out the async system. In Go, if you compute for a
           | while, the scheduler will let someone else run. You can get
           | all the CPUs working. This is well matched to writing web
           | back ends.
           | 
           | Rust's "Async" seems to be designed for a very specific use
           | case - a program running a very large number of network
           | connections, most of which are waiting. If you're doing
           | something where you need all available CPUs to get the work
           | done, it's a bad fit.
        
             | anderskaseorg wrote:
             | Rust has always had threads (https://doc.rust-
             | lang.org/book/ch16-01-threads.html), along with incredibly
             | powerful libraries like Rayon (https://github.com/rayon-
             | rs/rayon) to take advantage of them. You can even use
             | threads with async, if you want, using a multi-threaded
             | scheduler like Tokio with the rt-multi-thread feature flag 
             | (https://docs.rs/tokio/1.7.0/tokio/runtime/index.html#multi
             | -t...).
        
       ___________________________________________________________________
       (page generated 2021-06-17 23:00 UTC)