Hacker News new | ask | show | jobs
by first_amendment 3222 days ago
async/await is nice duct tape to integrate cooperative concurrency into a thread-based concurrency model but it has the drawback of creating an incompatible sub-language for functions within the parent language. Now every time you call a function, you have to check if it is a "coroutine"/ returns a promise or if it's a synchronous function.

This divides the language ecosystem and makes it hard for library writers to support both concurrency models.

Ask anyone who has done significant async/await coding in C#/Python. It gets messy fast.

Async/Await works a little better in JS because it always has had a cooperative concurrency model, standard library functions usually don't block (except sometimes in Node). Though it's still annoying to have to check whether a function returns a promise or not.

Languages that do this right are Haskell and Go and probably Elixer/Erlang. This usually requires eschewing LibC (which usually assumes a threaded concurrency model) and writing your own runtime.

2 comments

Yes, this is all discussed in the eRFC (which admittedly isn't what is linked here, but is the first link in the description, https://github.com/rust-lang/rfcs/pull/2033) and various threads on internals before it.

> writing your own runtime.

This is not a cost that Rust can bear, and so that option is just not possible. Unfortunately, there's no such thing as a free lunch.

I wonder if Rust could support pluggable concurrency models (with the accompanying runtime support). That's what Haskell does and it seems to work. At the outset that seems like a better approach than creating an incompatible sub-language. The rust-facing side of the standard library already abstracts away many platform details.
Trying this was in fact one of the reasons that Rust ditched its runtime years ago; it doesn't work. Well, it didn't work for Rust at least.

The RFC that made this change https://github.com/rust-lang/rfcs/blob/master/text/0230-remo...

Interesting. The problems section isn't really convincing to me though.

Especially, indirect function calls for IO functions seem fine since IO is usually much slower than a function call. Also binary size increase may not be a problem for people writing web-scale servers.

It would be great if the Rust compiler infrastructure provided the necessary hooks for re-implementing std::/using a different concurrency model and independent projects could make those "problems" trade-off decisions on a individual basis. Rust-core could just only support/ship the native threaded model to lower maintenance burden for themselves.

> The problems section isn't really convincing to me though.

It was a big enough problem that the language was almost forked over it.

> It would be great if

Rust is low-level enough that you can do this, and some people have. But then people have to use your package. std is only special in that it can use unstable code but be part of stable Rust; otherwise, it's just regular old Rust code. The community overall abandoned all of those other things and is coalescing around Tokio, which could also be considered this, in a sense. Or Rayon, if you want data parallelism rather than async IO.

Would it be possible to make functions generic over coroutines/async?

One of the worst parts of async is there's an infectious duplication of pretty much all library code where there are separate sync/async versions of everything. See C# DoXxx() & DoXxxAsync() everywhere, where the only difference between the two implementations are an annotation and some keywords sprinkled throughout. If rust can do the same thing without doubling the code/api surface/documentation across all libraries that would allay the fears of many opponents.

Wouldn't those trade offs just be picking a different language? I mean Rust is going for no runtime low level systems programming, if you add a runtime, then it brings what more to the table then say Haskell?
I approximately agree with you but there are lots of reasons I'd prefer Rust over Haskell. All data is thunked and boxed in Haskell, it's all heap-allocated and garbage collected, and polymorphism is through indirect pointers. Rust allows for "zero-cost" abstractions with a powerful memory-safe aware type system.

Erlang occupies a similar space.

Go is another option that's less like Haskell/Erlang but its type system is lacking/ inconsistent and it requires garbage collection.

So Rust has a real opportunity here that's not currently occupied by other languages.

Back in the day of Rust having a runtime, you couldn't call C functions, or functions potentially calling C functions, without worrying that it might indefinitely block one of the limited OS threads used by the runtime. With weirdly typed coroutines it's at least part of the type system.

I think Haskell happily spawns a new OS thread when one looks like it might be going to spend a while in C code?

same thing with Go as well.