Live data from Hacker News

How to think about async/await in Rust

cliffle.com

31–40 of 268 posts

Re: How to think about async/await in Rust

#31
post #15

I think Go got it right by inverting the logic around async/await. In Go you have to explicitly state that a function is to run in the background via "go fn(...)". This makes it much clearer that this code will execute concurrently. In the async/await world you can't tell by looking at a function call if it will block until it's done. Forgot an await? No compile error but your program might behave in weird ways. This…

It wouldn't work. In Go, it doesn't matter how deeply nested a call to a blocking function is, when you do "go f()" the runtime takes care of things. With async however, if "await" is the default, then as soon as an async function calls another async function, it would block, completely defeating the point of async in the first place. I guess you could flip the rules and say that within an async function async is the…

It would just make things more explicit. Whenever you want to obtain a future you'd have to add "async". The execution of async stuff would work the same just instead of having to explicitly "await" things you'd have to explicitly "async" things. Of course you can't change the way Rust does async/await now without having to rewrite all the async code so not going to happen.

Re: How to think about async/await in Rust

#32

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

Your rant reminded me of this classic post, which I believe shares your views but from different reasoning. From the discussion, you may be interested in looking at zig[0].

https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...

https://news.ycombinator.com/item?id=36597229 (fresh repost)

Discussed previously:

  https://news.ycombinator.com/item?id=8984648 (8ya)
  https://news.ycombinator.com/item?id=16732948 (5ya)
  https://news.ycombinator.com/item?id=23218782 (3ya)
  https://news.ycombinator.com/item?id=28657358 (2ya)

[0]: https://kristoff.it/blog/zig-colorblind-async-await/

Re: How to think about async/await in Rust

#33

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

It began in C# in 2012 - 5 years before JavaScript. And C# does threads. And made its way to JavaScript via Typescript (whose creator also created C#).

Maybe I'm misunderstanding what you mean by "It began in C#", but F# introduced async about five years before C# 5 was released.

Re: How to think about async/await in Rust

#34
post #15

I think Go got it right by inverting the logic around async/await. In Go you have to explicitly state that a function is to run in the background via "go fn(...)". This makes it much clearer that this code will execute concurrently. In the async/await world you can't tell by looking at a function call if it will block until it's done. Forgot an await? No compile error but your program might behave in weird ways. This…

You're talking about particular JS implementation problems, not general async/await problems. > In Go you have to explicitly state that a function is to run in the background via "go fn(...)". In Rust you have to explicitly `spawn` a task to detach it from the current coroutine and make it run in background. Typically this is much more costly than not spawning and executing async function concurrently as part of the…

    > foo();          
Only if you know that foo is an async function. You can't tell by the function call itelf.

    > warning: unused implementer of `futures::Future` that must be used
Interesting, I haven't seen this warning in the Rust codebase I worked a little with. I'll have to check the compiler settings. Anyways wouldn't it make sense to actually throw an error instead of just a warning?

    > Additionally there are certain things you are not allowed to keep across await points, e.g. mutex guards or other stuff that's not safe to switch between threads. E.g. using a thread-local data structure across await points might break, because you could be on a different thread after await. If await was hidden, you'd likely be much more surprised when the compiler would reject some code due to "invisible" await. 
Why couldn't the compiler clearly state the reason for the error though?

Re: How to think about async/await in Rust

#35

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

Implementing full-duplex protocols asynchronously is much simpler.

Re: How to think about async/await in Rust

#36

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

The alternative with threads on an IO-bound server eventually cycles back to async/await but with extra steps. You write synchronous request handlers until you notice IO-waits wasting all your thread time, add more threads to the pool, start hitting overhead from that, then implement greenthreads.

NodeJS did it right, and it's hard to call a 14-year-old technology a fad.

Re: How to think about async/await in Rust

#37

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

>Not specific to rust, but I think asynchronous programming in general is a hype.

Hmm, wasn't the whole point of doing things in event loop because it way out perform thread-based architecture? Like when Nginx way out performs Apache? On a single core CPU of course.

Edit: It is not that event-loop is better than thread-based, just in a web server scenario it just perform much better.

Re: How to think about async/await in Rust

#38
post #34

Earlier quoted context omitted.

You're talking about particular JS implementation problems, not general async/await problems. > In Go you have to explicitly state that a function is to run in the background via "go fn(...)". In Rust you have to explicitly `spawn` a task to detach it from the current coroutine and make it run in background. Typically this is much more costly than not spawning and executing async function concurrently as part of the…

> foo(); Only if you know that foo is an async function. You can't tell by the function call itelf. > warning: unused implementer of `futures::Future` that must be used Interesting, I haven't seen this warning in the Rust codebase I worked a little with. I'll have to check the compiler settings. Anyways wouldn't it make sense to actually throw an error instead of just a warning? > Additionally there are certain thing…

> Only if you know that foo is an async function. You can't tell by the function call itelf.

That's fair point, but traditionally you don't use blocking functions in async contexts at all. It is fairly easy to lint for by prohibiting some inherently blocking calls eg.g std::io, although they might sneak in through some third-party dependency.

This doesn't have an easy solution because Rust is a general purpose language that allows different styles of concurrency adapted best to the situation, instead of one-size-fits-all like Golang.

Rust has means to annotate functions so maybe there will be some automation to deal with that in the future, similar to how `#[must_use]` works now. E.g. `#[blocking]` or whatever.

> I'll have to check the compiler settings.

This is with default compiler settings.

> Why couldn't the compiler clearly state the reason for the error though?

Stating the reason is probably solvable problem, but there is another problem: what if 5 layers down the call chain something suddenly introduces a potentially blocking (awaiting) operation? This would mean that some code that previously compiled now has to stop compiling even though it hasn't changed and even though none of the signatures it uses changed. I guess it would break things like separate compilation.

And again, it would be less readable than it is now. Now it is fairly simple - you don't have to look down the call chain to know that something can do await.

Re: How to think about async/await in Rust

#40

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

> We left behind that paradigm in Operating Systems decades ago, and with good reason.

I'm curious, what reason?

I grew up on Python and C#, and only know async/await, never done real threading (C# async is threading and coroutines under the hood, Python is just coroutines, single-threaded). I find that way of writing code very elegant, as one can encode points of blocking/switching explicitly. A bit like encoding logic into the type system (cliffle has an article on the type state pattern, a good read!): the underlying async implementation can change without code adjustments.

Post reply on HN