It is a tool. Some tools make you more productive after you have learned how to use them.
I find it interesting how in software, I repeatedly hear people saying "I should not have to learn, it should all be intuitive". In every other field, it is a given that experts are experts because they learned first.
It forces programmers to learn completely different ways of doing things, makes the code harder to understand and reason about, purely in order to get better performance.
Which is exactly the wrong thing for language designers to do. Their goal should be to find better ways to get those performance gains.
> It forces programmers to learn completely different ways of doing things, makes the code harder to understand and reason about, purely in order to get better performance.
Technically, promises/futures already did that in all of the mentioned languages. Async/await helped make it more user friendly, but the complexity was already there long before async/await arrived
>And some languages learned the right lessons from the coloring problem. For example, Go deliberately chose goroutines over async/await, accepting a heavier runtime in exchange for no function coloring at all. Java’s Project Loom (virtual threads in Java 21) made the same bet: lightweight threads that look and behave like regular threads, so no code needs to change color. The Loom team explicitly cited function coloring as a problem they wanted to avoid.
>Zig went further: it removed its compiler-level async/await entirely and rebuilt around an Io interface parameter that i/o operations accept. [...] Though some argue that the Io parameter itself is a form of coloring.
>Language designers who studied the async/await experience in other ecosystems concluded that the costs of function coloring outweigh the benefits and chose different paths.
Green threads with a heavier runtime isn't the "right" lesson. Instead, it's a fundamental tradeoff of no-function-colors-ergonomics vs maximum performance. Some simply don't want to pay the "performance tax" or "interoperability tax" to avoid function coloring.
The following is copy-paste of previous discussion links and added Wayback Machine links because lobste.rs is often down. Anyone who thinks Golang made the "right" decision should read why early Rust developers tried green threads and abandoned them. The revised conclusion is that Go's design is a tradeoff that works for their devs but not for Rust. The author of this thread's article does not mention the issues the Rust devs were highlighting.
>Golang and Erlang successfully employ preemptive M:N models and there are no coloring problems in those languages.
Basically, Rust has lower-level performance constraints than Go that they didn't want to compromise on. (I.e. performance constraints of FFI C-interopt with predictable fixed stack space.) :
As counterpoint, Rust's original designer, Graydon Hoare, prefers "green threads". In a recent blog post (2023-June), he mentioned
that he understands why the Rust team got rid of it for performance reasons but he's not fully convinced of the ultimate tradeoff:
-> Async/await. I wanted a standard green-thread runtime with growable stacks -- essentially just "coroutines that escape, when you need them too". Possibly with a somewhat-embeddable outer event loop / IO manager library, but that's always going to be a little tricky. Go started and stayed here, but they had to do a bunch of gory compromises to make the FFI work and it leaves a lot of performance on the table and torpedoes a bunch of embedding opportunities. Rust started here too, and it got rewritten a couple times and eventually thrown out because of a lot of reasons but none which completely obviated the need (as evidenced by the regrowth of Async/Await and Tokio). I've softened my position on this and have a grudging respect for where Rust wound up (especially in enabling heterogeneous selects, which I think puts it in a similar and enviable expressivity class as Concurrent ML). But if I'm being honest I never would have agreed to go in this direction "if I was BDFL" -- I never would have imagined it could even work -- and still don't know that I think the result quite pays for itself. -- from https://graydon2.dreamwidth.org/307291.html
Function colouring, deadlocks, silent exception swallowing, &c aren’t introduced by the higher levels, they are present in the earlier techniques too.
Surely by section 7 well be talking (or have talked) about effect systems
I like async and await.
I understand that some devs don’t want to learn async programming. It’s unintuitive and hard to learn.
On the other hand I feel like saying “go bloody learn async, it’s awesome and massively rewarding”.
I can't follow that it's hard to learn and unintuitive
> It’s unintuitive and hard to learn.
Funny, because it was supposed to be more intuitive than handling concurrency manually.
Some come to async from callbacks and others from (green)threads.
If you come from callbacks it is (almost) purely an upgrade, from threads is it more mixed.
It is a tool. Some tools make you more productive after you have learned how to use them.
I find it interesting how in software, I repeatedly hear people saying "I should not have to learn, it should all be intuitive". In every other field, it is a given that experts are experts because they learned first.
Except you're hearing it from someone who doesn't have a problem handling state machines and epoll and manual thread management.
It IS intuitive.
After you’ve learned the paradigm and bedded it down with practice.
It is. A lot.
But concurrency is hard and there's so much you syntax can do about it.
What's awesome or rewarding about it?
It forces programmers to learn completely different ways of doing things, makes the code harder to understand and reason about, purely in order to get better performance.
Which is exactly the wrong thing for language designers to do. Their goal should be to find better ways to get those performance gains.
And the designers of Go and Java did just that.
> It forces programmers to learn completely different ways of doing things, makes the code harder to understand and reason about, purely in order to get better performance.
Technically, promises/futures already did that in all of the mentioned languages. Async/await helped make it more user friendly, but the complexity was already there long before async/await arrived
>And some languages learned the right lessons from the coloring problem. For example, Go deliberately chose goroutines over async/await, accepting a heavier runtime in exchange for no function coloring at all. Java’s Project Loom (virtual threads in Java 21) made the same bet: lightweight threads that look and behave like regular threads, so no code needs to change color. The Loom team explicitly cited function coloring as a problem they wanted to avoid.
>Zig went further: it removed its compiler-level async/await entirely and rebuilt around an Io interface parameter that i/o operations accept. [...] Though some argue that the Io parameter itself is a form of coloring.
>Language designers who studied the async/await experience in other ecosystems concluded that the costs of function coloring outweigh the benefits and chose different paths.
Green threads with a heavier runtime isn't the "right" lesson. Instead, it's a fundamental tradeoff of no-function-colors-ergonomics vs maximum performance. Some simply don't want to pay the "performance tax" or "interoperability tax" to avoid function coloring.
The following is copy-paste of previous discussion links and added Wayback Machine links because lobste.rs is often down. Anyone who thinks Golang made the "right" decision should read why early Rust developers tried green threads and abandoned them. The revised conclusion is that Go's design is a tradeoff that works for their devs but not for Rust. The author of this thread's article does not mention the issues the Rust devs were highlighting.
>Golang and Erlang successfully employ preemptive M:N models and there are no coloring problems in those languages.
The designer of Rust async/await says Go's green threads approach has an unavoidable performance tradeoff: https://news.ycombinator.com/item?id=37439886
Basically, Rust has lower-level performance constraints than Go that they didn't want to compromise on. (I.e. performance constraints of FFI C-interopt with predictable fixed stack space.) :
- https://lobste.rs/s/bfsxsl/ocaml_4_03_will_if_all_goes_well_... (https://web.archive.org/web/20250815150638/https://lobste.rs...)
- https://lobste.rs/s/y3fsrm/what_is_zig_s_colorblind_async_aw... (https://web.archive.org/web/20250402055806/https://lobste.rs...)
- https://lobste.rs/s/eppfav/why_i_rewrote_my_rust_keyboard_fi... (https://web.archive.org/web/20250412170821/https://lobste.rs...)
- https://news.ycombinator.com/item?id=21475154
As counterpoint, Rust's original designer, Graydon Hoare, prefers "green threads". In a recent blog post (2023-June), he mentioned that he understands why the Rust team got rid of it for performance reasons but he's not fully convinced of the ultimate tradeoff:
-> Async/await. I wanted a standard green-thread runtime with growable stacks -- essentially just "coroutines that escape, when you need them too". Possibly with a somewhat-embeddable outer event loop / IO manager library, but that's always going to be a little tricky. Go started and stayed here, but they had to do a bunch of gory compromises to make the FFI work and it leaves a lot of performance on the table and torpedoes a bunch of embedding opportunities. Rust started here too, and it got rewritten a couple times and eventually thrown out because of a lot of reasons but none which completely obviated the need (as evidenced by the regrowth of Async/Await and Tokio). I've softened my position on this and have a grudging respect for where Rust wound up (especially in enabling heterogeneous selects, which I think puts it in a similar and enviable expressivity class as Concurrent ML). But if I'm being honest I never would have agreed to go in this direction "if I was BDFL" -- I never would have imagined it could even work -- and still don't know that I think the result quite pays for itself. -- from https://graydon2.dreamwidth.org/307291.html