Most active commenters
  • echelon(5)

←back to thread

451 points birdculture | 19 comments | | HN request time: 1.715s | source | bottom
1. Havoc ◴[] No.43979116[source]
I thought it was quite manageable at beginner level…though I haven’t dived into async which I gather is a whole different level of pain
replies(1): >>43979133 #
2. echelon ◴[] No.43979133[source]
Async and the "function color" "problem" fall away if your entire app is in an async runtime.

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.

replies(6): >>43979257 #>>43979273 #>>43979433 #>>43979524 #>>43981881 #>>43986031 #
3. spion ◴[] No.43979257[source]
How are async closures / closure types, especially WRT future pinning?
replies(2): >>43979308 #>>43979705 #
4. sodality2 ◴[] No.43979273[source]
That’s not a solution to the coloring problem any more than making everything red was in 2015 (ie, all the tradeoffs mentioned in the article [0] still apply).

[0]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...

5. echelon ◴[] No.43979308{3}[source]
While I'd like to have it, it doesn't stop me from writing a great deal of production code without those niceties.

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.

6. bigstrat2003 ◴[] No.43979433[source]
"Just write everything async" is not remotely a good solution to the problem. Not everything needs to be async (in fact most things don't), and it's much harder to reason about async code. The issue is very much not overblown.
replies(1): >>43979650 #
7. lucasyvas ◴[] No.43979524[source]
It’s completely overblown. Almost every language with async has the same “problem”.

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.

replies(1): >>43979663 #
8. Salgat ◴[] No.43979650{3}[source]
Why is async code harder to reason about? I've been using it in C# and the entire point is that it lets you write callbacks in a way that appears nearly identical to synchronous code. If you dive into concurrency (which is a separate thing but can be utilized with async code, such as joining multiple futures at the same time), that parts hard whether you're doing it with async or with explicit threads.
replies(3): >>43980094 #>>43980325 #>>43982797 #
9. echelon ◴[] No.43979663{3}[source]
The "function color is a problem" people invented a construct that amplifies the seriousness. It's not really a big deal.
replies(1): >>43983621 #
10. mplanchard ◴[] No.43979705{3}[source]
Async closures landed in stable recently and have been a nice QoL improvement, although I had gotten used to working around their absence well enough previously that they haven’t been revolutionary yet from the like “enabling new architectural patterns” perspective or anything like that.

I very rarely have to care about future pinning, mostly just to call the pin macro when working with streams sometimes.

11. umanwizard ◴[] No.43980094{4}[source]
> Why is async code harder to reason about?

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.

12. Const-me ◴[] No.43980325{4}[source]
> I've been using it in C#

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.

replies(1): >>43984061 #
13. kaoD ◴[] No.43981881[source]
Async's issue is not coloring. It's introducing issues that just don't exist in sync code like pinning, synchronization, future lifetimes...

Coloring just exacerbates the issues because it's viral, not because coloring itself is an issue.

14. guappa ◴[] No.43982797{4}[source]
Because 1 mistake somewhere will make your whole application stuck and then you must locate which call is blocking.
15. Measter ◴[] No.43983621{4}[source]
My biggest issue with the whole "function colour" thing is that many functions have different colours. Like, these two:

    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`.

replies(1): >>43984091 #
16. echelon ◴[] No.43984061{5}[source]
None of this is scary.

> 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.

replies(1): >>43992400 #
17. echelon ◴[] No.43984091{5}[source]
Excellent retort!
18. notnullorvoid ◴[] No.43986031[source]
Agreed. Function coloring is a solution (not a problem), one that's better than the alternatives.

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.

19. guappa ◴[] No.43992400{6}[source]
> I can write async Rust without breaking a sweat

The difficult part comes when you try to do so "correctly".