Hacker News new | past | comments | ask | show | jobs | submit login
Async Rust Isn't Bad: You Are (n8s.site)
29 points by poidos 13 days ago | hide | past | favorite | 9 comments





That's amusing.

I've been arguing against "async contamination" for a few years now, because if you have both compute-bound and I/O bound work, async makes it harder. There's been enough pushback now to keep tokio from taking over the world. You can still use Rust without async. A year ago, it wasn't clear you'd be able to today.

There are situations where async is appropriate. Twitter (pre-Musk) used to ask applicants "A celebrity with a million followers sends out a tweet. How do you get it to all their followers within three seconds?" If you have to keep millions of connections open on your servers for reverse push notifications, you probably need async. Otherwise, probably not.

There's a broader problem. Go was built to do web server side stuff, and it's optimized for that use case. Google staff wrote all the important packages, and Google uses those packages internally. So even the obscure cases have been well-exercised. Go also has "goroutines", which, at some cost in overhead, provide the functionality of both full threads and async execution. Goroutines can block or go compute-bound without stalling out the system. Rust async executors cannot. Thus, Rust has the "function coloring" problem, but Go does not.

Rust is supposed to be for problems where C++ gets too tangled. Keeping concurrency straight in C++ is hard. Keeping ownership straight, and not getting dangling pointers or bad pointers, is hard in C++, too. Rust does fix those problems. So does Go, mostly. Garbage collection hides a multitude of sins.

In Rust, the formal semantics have to be right or the program won't compile. This means time spent getting the plumbing correct, and major revisions can be painful. It also means you need programmers who have some sense of formalism, which probably means a decent CS degree from somewhere. You shouldn't need such people just to make a web site work. It's like requiring electricians to have EE degrees.

Some things for which you'd think async might help don't benefit from it. One-many relationships are an example. Something that comes up regularly for me is this pattern in a game-like program.

- Discover that asset ABC is needed.

- Is it in memory already? No.

- In disk cache? No.

- Put on fetch queue, which has a priority order, and gets reordered depending on where the player moves to.

- Take off fetch queue. Recheck.

  - Is it in memory already? No.

  - Is it in disk cache? No.

  - Is it currently being fetched for some other need? No.

  - Are all the things that need it gone? No.

 - Fetch from network.

  - Are all the things that need it gone? No.

  - Do decoding and processing, which is compute bound.

 - Tell all the things that need it that it's now available.

  - If there weren't any, log the wasted effort.
This seems like a good candidate for async code. But async code is not that good at priority queuing, mixed compute bound and I/O bound work, or many-to-many waiting. So it's a bad fit. Async is kind of a one-trick pony - lots of more or less independent I/O bound tasks. That's a big niche, but a niche nevertheless.

> This "function coloring" is at odds with the stdlib functions, resulting in 3rd party crates for nearly everything.

Doesn't that point to what libc had to do, growing async safe versions of functions which previously were not?

The innate complexity of coding to async is never going away. It's harder with or without language support, almost all of the time, because the concepts inherent in coding embody patterns which are simple, if they are the only sequence of execution flow, and which are innately more complicated to think about, if you have to encompass doing them (and other things) at the same time.

  If this
  then that 
  otherwise other 
is always simpler than

  Try: this,
 
  Except: but if it fails at some unknown point of continuing execution then this path out,

  but meantime, lets progress that until we hit a point we have to rendezvous, 

  hey whats deadlock anyway??

Having grown up on a platform which did not offer preemptive threading, it still feels weird to me that people perceive async IO as being complex. The contortions one must go through to get decent performance while pretending there is any such thing as synchronous IO seem far more troublesome. Reality is an asynchronous, interrupt-driven state machine; the sooner you give up the illusion that you are in control, the sooner you can go with its flow.

What I read into this, is that if you walk into programming 101 and learn async and/or functional day #1, they make total sense.

If you walk into Pascal or Fortran, and have to acquire FP or async programming after, they make no sense.

This is also at the root of emacs-vs-VI and other debates. What you learn defines how you think.

I learned IF-THEN-ELSE flow, and GOTO. I do not find async easy to rationalise about, despite recognising it exists.

My partner says anyone who claims they can multitask "in real life" is lying, and does both things badly.


At an OS level its kind of a sad state of affairs, even on tiny devices the goto solution is an RTOS with threads (they aren't cheap memory or latency wise!)

If you look at what things like RTIC and embassy are doing I have to say its pretty appealing. Make programming the interrupt state machines a little nicer with async rust, and in RTIC's case without a chance of deadlocks.

That's pretty stellar, and at a footprint and memory usage that is far lower than even the smallest RTOS can muster.


The only thing I find confusing about async is that you have to migrate legacy code, and I don't really enjoy rewrites.

Otherwise, it looks basically the exact same as synchronous code, except background stuff only happens while you're await-ing something.

If there's a failure, the await raises an error, just like a sync function call would.

It was horrendous before await, JS nested callbacks were a nightmare, but all that is basically gone now.


IMO the biggest issue with Rust async is that it's unfinished. It took years for us to get async traits (in MVP state even now), we still don't have a stable async iterator interface, tasks can't borrow data (due to no un-leakable types), there are no standard abstractions for writing libraries compatible with several executors, no async destructors, etc etc. All these problems are fixable, but they haven't been fixed yet, and it's been far too long.

What makes the pain of async worth it is easy heterogeneous selects. Can't do that with threads, there's no equivalent.


Ada Lovelace probably told Babbage he was a bad programmer

> You wanna know what else didn't ship with async? The thing all your bullshit async websocket servers run on: Linux. Last time I checked, Linux, and the POSIX world in whole, seems to be doing just fine running the entire internet without it. signal, timerfd, epoll, kqueue exist. Guess what? That's all tokio and these runtimes are doing. They can't magically put something to sleep. The kernel is the one who's not bringing your CPU to a halt when your read isn't doing anything, not tokio.

Linux since 5.11/5.15 timeframe has shipped with an growingly speedy & capable async, io-uring. There are still security issues, to the degree that Google has started simply disabling io-uring. But for many application-servers, it feels like the risk today is minimal and it feels probable the situation will improve over time; I for one would not bet against io-uring.

If anything I think there's a huge possibility to write really good languages or standard libraries that dive headfirst into this better new world. I view there as being a huge late mover advantage, ready for the taking, now that operating systems have finally gotten good, now that we can offload work so effectively to the kernel. There's works like https://github.com/tokio-rs/io-uring to make use of io-uring, but I worry that it only covers slices of what could be possible, that it's just cutting out a single path of use, and that path is deeply constrained by path dependence. It'd be interesting to see what a (not necessarily Rust) stdlib would look like that was io-uring first, that tries to enable a significantly wide breadth of async usage.

I'm also interested & excited to see if wasm (& wasi) can start to make good use of the new excellent async that has bloomed so recently. Wasm's component-model is in the midst of trying to de-colorize function calls, allow async/sync code to call each other, which is quite an endeavor and something that - if it works - could provide great relief to this time of strife.

Briefly scanning the sample code here, evidence is that this person is also quite bad at async themselves. I don't see evidence that they've absorbed any of the lessons on the many many pitfalls of epoll. Or go read the systemd author's write-up on why epoll was hard for them. There's a reason that there are dozens of event-loop libraries out there; conceptually it seems simple but this stuff is incredibly difficult to get right! Also, a quick scan hasn't shown any notable epoll improvements in the past ~8 years; would love to be found wrong on that. https://idea.popcount.org/2017-02-20-epoll-is-fundamentally-... https://news.ycombinator.com/item?id=13736674 http://0pointer.net/blog/introducing-sd-event.html . To the counter, there is also a epoll defense using EPOLLET (edge triggered): https://gottliebtfreitag.de/blog/2019/09/14/Epoll.html .




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: