I dislike how this stuff "infect" all your code calls:
https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
And the problem is rust is that we already have other stuff that infest everything: Borrows, mutability, ownership...
I don't think anyone disagrees that the annotation of these concepts play a very heavy role in the type system and cognitive burden of writing code. But there are really three alternatives you can do here:
1. Do nothing, and rely on programmers to manually remember how to do all the necessary bookkeeping themselves. This is better known as "C", and the sheer amount of CVEs and other problems in code written in C is ample evidence that this approach just doesn't work.
2. Make the runtime do the automatic programming. JS is the exemplar of the pattern here. But this approach requires the VM to do garbage collection itself (which adds uncertain hidden overhead), generate code to handle arbitrary shapes (which can result in hidden performance cliffs). And heavily dynamic languages also turn compiler errors (such as fat-fingering a variable name) into runtime errors that can be accidentally ignored.
3. Shift the annotation into a mandatory part of the language, as Rust does. It makes the cognitive burden of writing the code higher, it requires more coding, but the resulting code tends to have the highest performance and lowest bugs of any of these approaches.
The tradeoff you have to make is between "no safety", "compile-time safety", and "runtime safety"; the costs you have to weigh are the costs of testing to uncover bugs, the costs of programmer cognitive and annotation burden, and the (often hidden!) costs of the runtime to dynamically enforce the conditions.
Rust has chosen to live in the camp of low implicit runtime cost and high compile-time safety, and it is really to pay the price for that with extra annotation burden. You may disagree with that choice, but it is a conscious choice that has been made.
This is what I want. I just not like the specific implementation of awaits. I'm not much against it: i could live with it (I have used C#/F#/JS that is similar).
The thing is that still split the world even if you don't want it.
I think CSP/Actors have lesser cognitive load but of course I don't know how make them work without a runtime..
Unlike in JS, it really doesn't. In Rust, at any point you have the option to run a Future returned from an async function to completion in a sync function (which blocks the thread) and stop the spread of async.
While also making your software more resilient to failure because error handling is easier as you can contain the code into small processes, communicate through messaging, let processes fail, and handle/restart them from the supervisor. Using the OTP supervisor model encourages you to think through those things, like error handling and the lifecycle of your code, so it doesn't impact other parts of your running app. A lot of exception handling in other languages is often bolted-on after-the-fact and isn't part of the typical design, at least for more inexperienced developers.
It's best to commit fully to either a full-featured VM or Rust/c low level OS style IMO.
The latter group can still support a more complex actor model with libraries and frameworks (ie, https://actix.rs/) which is more flexible but nothing beats having it be the 'standard' philosophy of the language and fully baked into the runtime.
Steve Klabnick provided some useful context on that thread for the Rust decision specifically. More generally though, you've nailed my primary misgiving:
> Synchronous or asynchronous is something up to the caller to the decide – not to the function itself.
Erlang/Elixir and Go place the decision with the caller, not the callee. That just seems much more sensible.
An alternative is perhaps to see async/await as dataflow going through the awkward teenager stage. Maybe one day we'll wake up and languages in general will have fully embraced dataflow variables as first order constructs.
[0] https://news.ycombinator.com/item?id=20400991