top | item 39102078

Avoid Async Rust at All Cost

46 points| jmakov | 2 years ago |blog.hugpoint.tech | reply

62 comments

order
[+] seanhunter|2 years ago|reply
I have seen this exact same argument in python and elsewhere as well. Any time anyone writes a rant against async and suggests threads as an alternative, you know they didn’t really understand what async was for and were using it for something it’s not really for, so you know somewhere in their rant they will say the performance improvement they expected didn’t happen.

Async (in any language) is not a panacea. Async is for allowing multiple things to make progress simultaneously that would otherwise be blocked on I/O. If you thread them your threads will be independently blocked on I/O and you will have additional locking overhead. If you have an embarrassingly parallel task and you aren’t blocked on I/O of course async will be slower than pure parallelism because that’s not what it’s for. It’s almost literally so you can have one async thread consuming exactly 1 CPU doing all the I/O and it will all make good progress.

[+] lr1970|2 years ago|reply
Threads are for when you COMPUTE a lot, while async/await are for when you WAIT a lot.
[+] zzleeper|2 years ago|reply
> The purpose of flagging is to indicate that a story does not belong on HN. Frivolous flagging—e.g. flagging a story that's clearly on-topic by the site guidelines just because one personally dislikes it—eventually gets an account's flagging privileges taken away. But there's a new 'hide' link for people to click if they'd just like not to see a story.

https://news.ycombinator.com/item?id=12173836

This story seems to very much belong on HN. Just because the statement is opinionated and some users don't like it, it doesn't mean that we can't debate about its merits.

[+] IshKebab|2 years ago|reply
Yeah there's no reason for this to be flagged. Bad look for Rust IMO if lots of Rust users are flagging valid criticisms.
[+] ernst_klim|2 years ago|reply
I also think that Async Rust is a major disadvantage and overall a mistake.

It feels like Rust is trying to be "The Language" suitable for both low-level system programming and high level application development.

But you can't do both. Rust will never be as ergonomic and simple to cook as Java, Go, OCaml, Scala, Erlang/Elixir and other high level languages. Yet this async split brings the perilous language schism somewhat akin to D's GC/non-GC dialects, where people have to write and maintain two versions of libraries. And I doubt that parametric async will solve the problem fully.

[+] noxs|2 years ago|reply
I disagree about ergonomics. Switching to rust allowed us to focus on the real application/business logics rather than spending time worrying about GC lag, performance, exceptions, null references, memory leak etc. plus the toolchain is much nicer than most other languages.
[+] valenterry|2 years ago|reply
I agree, however... async is also super important for high performant code, which is one of the goals of Rust no?
[+] cmrx64|2 years ago|reply
I’ve recently (past year) been diving deep into async Rust and the modern Rust ecosystem after a several year hiatus (last active 2013-2016, pre-async). maybe this advice applies to a small category of application developers, but this take overall feels reactionary (versus constructive) and immature (cites OS primitives that don’t approach the same design space). There are pains with async Rust but the community should lean into trying to solve them. I personally don’t feel the pains as severely as described…

Deferred computation is a primitive, and threads do not solve it.

[+] bheadmaster|2 years ago|reply
I feel that, from a language theory level, it should be possible to implement functions that can be called in both sync and async contexts, removing the need for function coloring.

Any fundamentally blocking operations could be forced by the compiler to have have two implementations - sync (normal) and async, which defers to some abstract userspace scheduler that's part of the language itself.

[+] the_mitsuhiko|2 years ago|reply
I think the better way to think about async Rust is to use it when it's beneficial to developer productivity and to avoid it when not. There are quite a few situations where it makes the code easier compared to alternatives you could come up with.

I don't think for a second that async Rust should be picked for performance reasons.

You get a feeling for what is a good use of async and bad use of async relatively easily these days as the ecosystem is maturing.

[+] zaphar|2 years ago|reply
It is increasingly harder to avoid async Rust if you do any form of IO. Most of the useful io based crates assume you are doing async with a very minor amount of them giving you a non async api. Out of the ones that do they are bundling an executor to power that api because they don't want to implement it twice.

I think part of what is feeding this sort of backlash against it is the way that it creates two different rust ecosystems. One of them, the non async version, being decidedly a second class citizen.

[+] baq|2 years ago|reply
What arguments are there for async if not performance? Threads/fibers/gofuncs/actors/... are easier to reason about. Async is super helpful to avoid overhead of thousands of threads, but makes just about everything else harder.
[+] amelius|2 years ago|reply
> I think the better way to think about async Rust is to use it when it's beneficial to developer productivity and to avoid it when not.

How do you know if what is best doesn't change as the project you're working on progresses and your manager tosses in new requirements?

I'd say better pick a technique (or even language) that works all the time.

[+] Ciantic|2 years ago|reply
Async solves different problems, you can, for instance, have just a single-threaded CPU and still have a nice API if you have async-await. It might not be so cool at a higher level as Go's approach of channels and threads, but it's cool in embedded, read this:

https://github.com/embassy-rs/embassy?tab=readme-ov-file#rus...

"Rust's async/await allows for unprecedently easy and efficient multitasking in embedded systems. Tasks get transformed at compile time into state machines that get run cooperatively. It requires no dynamic memory allocation, and runs on a single stack, so no per-task stack size tuning is required. It obsoletes the need for a traditional RTOS with kernel context switching, and is faster and smaller than one!"

I'm just toying with Raspberry Pi Pico and it's pretty nice.

Go and Rust have different use cases, the async-await is nice at a low level.

[+] IshKebab|2 years ago|reply
I don't disagree with any of this, though it might be worth mentioning that async can be useful on platforms that don't support threads, e.g. embedded or WASM.

I doubt it would have been added to the language if it was just for those use cases though.

[+] enbugger|2 years ago|reply
I would say something similar about the concept of ECS (entity component system). Especially the reasons not to use it:

- Leaky abstraction - check

- Violation of the zero-cost abstractions principle - check

- Major degradation in developer's productivity - check

- Most advertised benefits are imaginary, too expensive (unless you are AAA) or can be achieved without it - check

[+] mrkeen|2 years ago|reply
Maybe Async Rust is bad, I haven't tried it!

But as for the arguments:

> But even in 1999 Dan says this about cooperative M:N models:

>> At one point, M:N was thought to be higher performance, but it's so complex that it's hard to get right, and most people are moving away from it.

It is higher performance. If you have M jobs and you can get N workers to work on them at the same time, you win!

It is also complex. So if you want the feature, let the smart people working on runtime figure it out, so that each team of application developers in every company doesn't invent their own way of doing it. If not in the runtime, then let library developers invent it, so there's at least some sharing of work. (Honestly I probably prefer the library situation, because things can improve over time, rather than stagnate.)

> Many operating systems have tried M:N scheduling models and all of them use 1:1 model today.

Nope! At the application level, M is jobs and N is threads. But at the OS level, M is threads and N is cores. Would I be exaggerating to say that doing M:N scheduling is the OS's primary purpose?

> but how come M:N model is used in Golang and Erlang - 2 languages known for their superior concurrency features?

These examples are "the rule", as opposed to "the exceptions that prove the rule".

> The Coloring Problem

I'm sick of the What Color Is Your Function argument. The red/blue split exists, and not just for asynchrony. Your language can either acknowledge the split or ignore it:

* A blocking function can call a non-blocking function, but not vice-versa.

* An auth'd function can call a non-auth'd function, but not vice-versa.

* An impure function can call a pure function, but not vice-versa.

* An allocating function can call a non-allocating function, but not vice-versa.

* A subclass can call into a superclass, but not vice-versa.

* A non-deterministic function can call a deterministic function, but not vice-versa.

* A exception-throwing function can call a non-exception-throwing function, but not vice-versa.

Even the dependency inversion principle works this way: it's a plea for concretions to call abstractions, and not the other way around!

Trying to remove the red/blue split will not work, and you'll only be pretending it doesn't exist.

The "solution" (if you can call it that) is simply for library writers to expose more blue code and less red code, where possible. If your language acknowledges that red and blue are different, then application developers have an easier time selecting blue library imports and rejecting red ones. Which is somewhat aligned with the article's title. But application developers can do whatever - red/blue, go nuts.

[+] bheadmaster|2 years ago|reply
> Trying to remove the red/blue split will not work, and you'll only be pretending it doesn't exist.

Go managed to so it. What exactly would "you're only pretending it doesn't exist" mean in context of Goroutines?

[+] davidhs|2 years ago|reply
This article feels overblown. Is async Rust perfect? No, far from it. It feels like a MVP that Rust's developers have neglected for a while. Hopefully picking up some steam these days with partial implementation of async functions in traits. But there still problems with it.

Async Rust is rather nice to use when you're writing a web server. Structuring your code in an async manner is honestly very useful. Writing a composite Future or a Future state machine by hand is super tedious. Async makes most of that pain go away.

[+] paholg|2 years ago|reply
The first sentence past the "list of reasons" is:

> Async Rust is objectively bad language feature that actively harms otherwise a good language.

This is an objectively false statement :) and is so inflammatory that I don't see much of a reason to read past it. Especially since I, and many other people, have been using async Rust in production quite happily for years.

[+] noxs|2 years ago|reply
Many of the statements are outdated, plus that async isn’t really a completed feature yet.
[+] fd00|2 years ago|reply
The OP doesn't seem to know what the Go and Erlang/BEAM runtimes are or what they do. One of their primary tasks is managing _async tasks_. 'Just use epoll'... Please, make it it a livestream, I'll buy popcorn and We'll all watch you reinvent rust, go and erlang.
[+] junon|2 years ago|reply
> Leaky abstraction problem which leads to "async contamination".

There's not much else of a way to do it any better. Not sure your exact gripe here, other than dogmatic.

> Violation of the zero-cost abstractions principle.

It's not a principle, it's just a benefit of Rust's design that you get often but not always. `Clone` is not zero cost, should we throw that out too?

> Major degradation in developer's productivity.

Yawn, speak for yourself. I implemented incredibly extensive firmware with Embassy (async embedded framework) in months instead of years for a custom PCB I made. Async was literally the last thing on the list that caused problems - in fact it sped up my productivity and reduced power usage of the board overall.

> Most advertised benefits are imaginary, too expensive (unless you are FAANG) or can be achieved without async

No, they cannot. You are so confidently incorrect to an impressive extent.

Stopped reading after that section. This person has some bone to pick and left level-headedness at the door in doing so.

[+] sshine|2 years ago|reply
> `Clone` is not zero cost, should we throw that out too?

Am I mistaken when I say that `ToOwned` is sometimes zero-cost?

And that `.to_owned()` vs. `.clone()` is free when the trait instances allow it?

[+] IshKebab|2 years ago|reply
> `Clone` is not zero cost

I think you've misunderstood. "Zero cost abstraction" is not the same as "zero cost".