Go is the only language that gets concurrency right.
Rust async is ugly but it’s ugly because async is ugly and as a systems language there are limits to how much of that ugliness it can hide.
If we could fix threads to scale better at the OS or core library level we’d save insane amounts of developer time.
- ad hoc callbacks, which had a great Result-ish type signature but really do warrant the “hell” in “callback hell”
- Promise APIs, which are semantically equivalent to async the keyword, unless you care about call stacks, and have a lot of the same hellish problems as the ad hoc callbacks they were meant to address (less nesting! same everything else!)
- Um fibers? Good luck making sense out of whatever that’s doing. It’s a good idea, but it’s also all opaque magic when you try using it.
- Actual threads and child processes… there are valid use cases, and they’re worth pursuing if you have a valid use case, but the facilities for development with them are basically “here’s a bunch of low level concepts that closely mirror their system level counterparts, hope you know/figure out what you’re doing!”
The author of esbuild gave up trying to code esbuild with nodejs/worker threads and switched to Go for a less limited/restricted concurrency environment.
Okay with that clarification I can agree it’s a good model, given its constraints. The event loop with async IO in the abstract is a good way to model a single process/thread workload for many use cases that fit it.
> I'm not sure if this abstraction is better than all of the available alternatives if you compare it to high-level features multi-threaded runtimes offer like thread-safe collections, atomic updates, concurrent hash maps, immutability, structured concurrency, etc... as in Java/Clojure.
Clojure’s solution to concurrency is a breath of fresh air, regardless of your execution environment, because its state transactional semantics are great whether your concurrency is in one process/thread or spread across many. I can’t speak to typical Java solutions, but my general sense is they’re higher level and more powerful than Node’s for actually crossing process/thread boundaries, but subject to most of the shared state problems Java has even in a single process/thread.
> The author of esbuild gave up trying to code esbuild with nodejs/worker threads and switched to Go for a less limited/restricted concurrency environment.
After a lot of exploration of Node worker threads, I’d probably similarly look elsewhere if I had a workload suited to it. You can do a lot with Node worker threads with a lot of special tuning for a use case, and I even have some proof of concept code demonstrating that it can be much better than common usage. But I put it on hold because the complexity of making it perform well is very high compared to optimizing the single thread default.
Lol. I guess you haven't seen Elixir.
When you couple this with a (mostly) bunch of noob devs who have been told Go is super safe because of it's type safety, you have a recipe for disaster.
The combination of language features and surrounding social effects means, once you pursue concurrency, Go abruptly becomes one of the most dangerous languages to use for any industry where subtle errors of value are a problem ... like anything to do with money.
p.s. No slight intended to Elixir, it avoids all these risks.
Java and Elixir both have taken the non-async approach. C# is actively looking into it as well now too: https://twitter.com/davidfowl/status/1532880744732758018
var a = new A();
A a = new();
A a = new A();I haven't written C# in a while, but I don't remember seeing that one. I skimmed the C# grammar and didn't see it there either, but I only glanced and might have missed an entire section.
I'm honestly surprised they haven't add the ability to drop the `new` keyword like Kotlin:
val a = A();