Hacker News new | ask | show | jobs
by sebastiandb 1065 days ago
This article about async in Python helped me understand it pretty well, since it explains them in terms of coroutines, which are very intuitive for me: https://mleue.com/posts/yield-to-async-await/

Another thing that helps me get it is comparing it to the continuation passing style, where you never return from a function, you just take an argument that's basically a function pointer bound to an environment, and at the end of the function, instead of returning, you call your input function, giving it another function and environment as input, repeating the cycle. It's very similar to the transformation of callbacks within callbacks within callbacks pattern in JavaScript to the async/await pattern.

2 comments

The thing is, we tried coroutines in C (embedded) y-e-a-r-s ago. It was all the rave for a bit. There were a couple different macro/libraries you could use with duff's device and other trickery to get coroutine-ish things in C.

Maybe the implementation just wasn't up to where it needs to be with these newer/slicker/more integrated versions, but mine (and others') issues with them wasn't the weakness/caveats of the implementation, but rather with the mess of spaghetti it made as your coroutine use grew with any degree. In onesie twosies under nice demo cases (look ma, I get some data from the intertubes with this syncy thing), they're great, but my experience was that they're a mess when scaled.

I'm happy to be proven wrong. I get to use them a bunch in Kotlin, I'm trying not to be a victim of my experience. I'm still on the fence.

Those libraries were always somewhat of a hack. Async Rust is an official language-backed syntax.
OP did say:

> but mine (and others') issues with them wasn't the weakness/caveats of the implementation, but rather with the mess of spaghetti it made as your coroutine use grew with any degree.

Once you realize async/await is just sugar over the familiar callback hell, a lot of the mystery fades away and it's easier to groc.
Rust async is a bit different than in other languages. It's more like sugar over state machines instead of sugar over callbacks.

This is what makes it work nicely on embedded. The compiler-generated state machines are structs with fixed size so they can be statically allocated. Callbacks would have to be heap-allocated and garbage-collected/refcounted.

(disclaimer: Embassy maintainer here)

> It's more like sugar over state machines instead of sugar over callbacks

they are equivalent [1]. There are scheme compilers (a language with have first class continuations and often heap allocated stack frames) that compile everything down to a giant C switch statement.

[1] well, continuations are strictly more powerful of course, but the stackless subset needed for async/await is the same.

This misses the actual async part, which is more like polling a task queue. Callback hell sugar is only a thing in langues that already have event loops built in (i.e. JavaScript, which I assume is also what you're referring to)
The point is that it actually is insightful to think of it as callback sugar. That will give you better understanding of how the threading is handled when the calling method yields to the callee and conversely how it must be different when the caller isn't yielded.
It generally applies to eventloops, not just to lanugages with a builtin event loop. E.g. it certainly also applies to raw boost::asio which uses callbacks, libuv, libevent, QT, GObject, Netty, etc.
That’s true in JS but less so in a language like Rust where there are threading implications.
There are no threading implications to async in Rust. The executor you're using may add some requirements on your futures because it wants to run them on multiple threads[1], but that's not related to async itself, and you can always use a single threaded executor if you don't want these limitations (and they doesn't apply to embeded anyway).

[1] namely, your futures will need to be Send + 'static.

I'm not as familiar with Rusts's implementation but even in C# it's mostly true. Threads are only hit when a callback is not directly awaited. There's more to it but it starts you down the right path, I think.
Or realize CPS, channel and actor model are basically equivalent.