So much time is spent on async because it's important and most other languages have ignored it for a long time.
Ever tried libuv in C? That was just a nightmare to work with, callbacks within callbacks within callbacks.
Rust's approach to async is the best I've seen so far in any language, even high level languages like javascript that depend on async to even function.
I'm not a Rust programmer but have used async/await in both Python and C#. I've also written concurrent code in Erlang. I'd choose the Erlang approach over async/await every time. One concurrency primitive - the process - and an ergonomic, coherent set of supporting features (message passing, supervisors). No function colours and all the baggage that comes with that. Less well discussed but no less important: it puts concurrency decisions in the hands of the function caller, not the implementer.
I understand Rust's focus on zero cost abstractions and, whilst I wouldn't pretend to understand the innards and consequences, get why green threads might not be compatible with that. OTOH that restriction doesn't hold for languages with a runtime like C# and Python. I'm increasingly convinced the compromises of async/await make it a poor language design for the concurrency problem when the language has a runtime.
Perhaps we'll see some comparitive studies now Java has green threads. Erlang is different from C#/Python in many ways so straight comparison is hard. Java is much closer to C# so should be a much better basis for comparison.
You'd do it like this: https://github.com/boostorg/cobalt or like this: https://github.com/danvratil/qcoro
> Async style language features are a compromise between your execution model being natively compatible with the 1:1 C ABI, C standard library, and C runtime and a M:N execution model. C++ async suffers from the same issues, except it’s not as strict in terms of lifetime safety (not a good thing). The cost for the native compatibility with the C/system runtime is the “function coloring” problem.
> Go, Haskell, and I assume Erlang make the other compromise. They eschew the C ABI and runtime completely and implement their own standard library. All code ends up being color-clean. The cost is that integrating with code outside their ecosystem is complex and slow.
And the "bit more wasteful" part is a non-starter because people want to use async in embedded contexts.
> For those who don’t know, there was a big debate whether the await operator in Rust should be a prefix operator (as it is in other languages) or a postfix operator (as it ultimately was). This attracted an inordinate amount of attention - over 1000 comments. The way it played out was that almost everyone on the language team had reached a consensus that the operator should be postfix, but I was the lone hold out. At this point, it was clear that no new argument was going to appear, and no one was going to change their mind. I allowed this state of affairs to linger for several months. I regret this decision of mine. It was clear that there was no way to ship except for me to yield to the majority, and yet I didn’t for some time. In doing so, I allowed the situation to spiral with more and more “community feedback” reiterating the same points that had already been made, burning everyone out but especially me.
The whole point of Rust is to not be a bit more wasteful
The tl;dr is: "The previous async approach ended up not working, and had to be removed. It's currently an incredibly hard problem with no clear rodemap. The plan is to get there eventually."
That being said, even if we ignore wastefulness, have you tried async programming in OCaml or in Haskell? You immediately enter a CPS/monadic nightmare that makes programming way more complicated, debugging extremely hard and doesn't deal too well with errors.
These are the hard async problems that Rust is attempting to solve. Performance isn't the main blocker here.
For instance, there are many system-level data structures that are not allowed to migrate from one thread to another (e.g. Linux mutexes or Rust's Rc) or sometimes from one core to another. If you adopt uncolored async (unless perhaps you're using a thread-per-core scheduler), you just can't manipulate these data structures. At all.
Which means that you can't be a system programming language (for some definition of system programming).
By the way, if you wish to test uncolored async in Rust, you can find an implementation here: https://github.com/Xudong-Huang/may .
Rust chose a different path that is not more complex, but the complexity lies elsewhere.
I never understood the fascination for working with cutting edge languages; to me it just increases the likelihood that you're going to find language errors or library errors and get tied up for ages helping to refine the environment. Plus the relatively small size of the developer community and supporting literature available.
Maybe it's a dog people thing, the sort of people that get into it are the sort of people that want to go home and have a dog with infinite energy bouncing up and down all the time.
Just exhausting to even think about.
Whatever about anything else, Rust has definitely moved past "cutting edge"
But I'm always thinking that the JVM is a pretty solid platform that has not yet reached its full potential. It gets a bad rap because of the hellhole that enterprise software is. But come on, look at android, look at games like Minecraft. Solid projects, written in large part in Java.
I know that Rust does not have a runtime and it does not have a spec but I think the questions posed in the article hint at the need for either -- or even both at the same time (i.e. "make a spec for the third party runtime implementors").
I am not saying "imitate Golang or Erlang". I am saying: "just pick a side already".
On a more intuitive level: to me it feels like Rust tries to be everything for everyone, and I think many of us know that will never work.
Choose. Commit. Double down. Make it work. Golang and Erlang did. Rust can do it as well.
Check my other sibling comment where I reply to the OP's author.
Being automatically cancellable is part of the value proposition of the future abstraction: it lets you abandon unnecessary work automatically. This post is about a limitation on cancellation (cancellation itself must be synchronous).
To me, your comment appears to be a sort of vibey free word association. Microcodes? Pick a side? What on earth are you actually trying to express??
> This also alleviates the need for considering any sort of question about “what happens if you cancel the cancellation future,” and whether that is recursive or idempotent: once a future begins canceling, “canceling” it again is idempotent, because its already canceling; there is no second future to cancel.
I think the misunderstanding they had is that what you call "async cancellation" requires a second future (which is implicitly constructed from `poll_cancel` or something like that), whose entirely purpose is to run the cancellation code of the first future. If this were the case, then we'd have to ask the question "What happens if the cancellation future is cancelled? Who cleans up after it?"
I don't have enough experience with async Rust in practice yet (sadly), so I was also tripped up when reading at first. I think that you call it "async cancellation" makes people think that it's a separate future, even though (from the type signature of `poll_cancel`) it should be completely clear that it isn't.
Sorry, I hope I got everything right, and that this clears up what I believe to be the misunderstanding for everyone involved!
Again, my opinion only. Feels like too much complexity for no payoff.
The other person replying to you understood me correctly.
Guy is in a bubble. He doesn't realize that 99% of rust programmers don't have the experience he does.
I very rarely run into borrow check errors, and if I do they are usually easily solved. From my impression that's the same for most semi-experienced Rust devs.
If people get stuck here, it is because they don't understand that the dreaded borrow checker error is (generally) about large, systematic code design patterns, not about local changes.
They try to appease the borrow checker by making small, local changes, which doesn't end up working, since borrow checker issues are about a fundamental issue in the ownership-design of your codebase. Once someone explains this to you, it's really not that hard.
This is not to suggest that async models are a bad idea. They just have a higher level of intrinsic complexity and language support is immature. The benefit is qualitatively better scalability and performance, so there is a purpose behind using async models.
Any concurrency model has tightly coupled & specific semantics about scheduling and cancellation: that's what a concurrency model is meant to provide. The semantics of "synchronous code" are just treated as natural by programmers because it's what they've been taught their whole lives, but someone had to invent that too (Edsger Dijkstra & Tony Hoare, specifically).
If all you mean to say is "the async model means whether a function synchronizes with a concurrent process becomes part of its contract", yes that's true, but the idea that this is "eroding modularity" and not "including essential facts about function behavior in its type signature" is an assertion of design principle, not a statement of fact.
There are a number of other proposals linked to this issue that can be referenced from that thread. I hope there is a next generation async model for future languages that is truly simple, because this all makes me think footguns are endemic to the current approach (which is broadly the same in Rust and Swift).
This happens all the time. For example, cancellation in the middle of sending a HTTP request. The connection is now unusable and must be closed. Without cancellation the connection returns to a state where it can be used and is re-added to a pool.
Note that this is true for UNIX file descriptors, but not for C++ streams: an error when closing may indicate that the stream you closed had some buffered data it failed to flush (for any of the reasons that a failed write might come from).
In that case, you sometimes do want to take a different codepath to avoid data loss; eg, if your database does "copy file X to new file Y, then remove X", if closing/flushing Y fails then you absolutely want to abort removing X.
In the case of Rust code, the method you want is `File::sync_all`, which returns a Result for this exact purpose.
That would be Erlang.
I wonder what's so different about rust that they can't solve it in the same way.