Almost 90% of the Rust I write these days is async. I avoid non-async / blocking libraries where possible.
I think this whole issue is overblown.
[0]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
When it came time for me to undo all the async-trait library hack stuff I wrote after the feature landed in stable, I realized I wasn't really held back by not having it.
I’m not calling this the pinnacle of async design, but it’s extremely familiar and is pretty good now. I also prefer to write as much async as possible.
I very rarely have to care about future pinning, mostly just to call the pin macro when working with streams sometimes.
I don't know about C#, but at least in Rust, one reason is that normal (non-async) functions have the property that they will run until they return, they panic, or the program terminates. I.e. once you enter a function it will run to completion unless it runs "forever" or something unusual happens. This is not the case with async functions -- the code calling the async function can just drop the future it corresponds to, causing it to disappear into the ether and never be polled again.
One reason why async-await is trivial in .NET is garbage collector. C# rewrites async functions into a state machine, typically heap allocated. Garbage collector automagically manages lifetimes of method arguments and local variables. When awaiting async functions from other async functions, the runtime does that for multiple async frames at once but it’s fine with that, just a normal object graph. Another reason, the runtime support for all that stuff is integrated into the language, standard library, and most other parts of the ecosystem.
Rust is very different. Concurrency runtime is not part of the language, the standard library defined bare minimum, essentially just the APIs. The concurrency runtime is implemented by “Tokio” external library. Rust doesn’t have a GC; instead, it has a borrow checker who insists on exactly one owner of every object at all times, makes all memory allocations explicit, and exposed all these details to programmer in the type system.
These factors make async Rust even harder to use than normal Rust.
Coloring just exacerbates the issues because it's viral, not because coloring itself is an issue.
fn foo() -> String
fn bar() -> Result<String, Error>
I can't just treat `bar` the same as `foo` because it doesn't give me a String, it might have failed to give me a String. So I need to give it special handling to get a String. async fn qux() -> String
This also doesn't give me a String. It gives me a thing that can give me a String (an `impl Future<Output=String>`, to be more specific), and I need to give it special handling to get a String.All of these function have different colours, and I don't really see why it's suddenly a big issue for `qux` when it wasn't for `bar`.
> The concurrency runtime is implemented by “Tokio” external library.
Scare quotes around Tokio?
You can't use Rails without Rails or Django without Django.
The reason Rust keeps this externally is because they didn't want to bake premature decisions into the language. Like PHP's eternally backwards string library functions or Python's bloated "batteries included" standard library chock full of four different XML libraries and other cruft.
> instead, it has a borrow checker who insists on exactly one owner of every object at all times, makes all memory allocations explicit, and exposed all these details to programmer in the type system
Table stakes. Everyone knows this. It isn't hard or scary, it just takes a little bit of getting used to. Like a student learning programming for the first time. It's not even that hard. Anyone can learn it.
It's funny people complain about something so easy. After you learn to ride the bike, you don't complain about learning to ride the bike anymore.
> Rust is very different.
Oh no!
Seriously this is 2025. I can write async Rust without breaking a sweat. This is all being written by people who don't touch the language.
Rust is not hard. Stop this ridiculous meme. It's quite an easy language once you sit down and learn it.
The "function coloring problem" people are harming entire ecosystems. In JS for example there are very popular frameworks thay choose to wrap async in sync execution by throwing when encountering async values and re-running parts of the program when the values resolve. The crazy part with these solutions trying to remove coloring, is they don't, they hide it (poorly). So instead of knowing what parts of a program are async you have no idea.