Hacker News new | past | comments | ask | show | jobs | submit login

We do not want red and blue functions. Any language that implements async / await as coroutines instead of green threads is making a fundamental CS mistake. https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...

Concurrency's correct primitive is Hoare's Communicating Sequential Processes mapped onto green threads. Some languages that have it right are Java (since JDK17 - Java Virtual Threads), Go, Kotlin.




Always with the blooming red and blue functions. You can say exactly the same thing about const.

The fact that a function can perform asynchronous operations matters to me and I want it reflected in the type system. I want to design my system on such a way that the asynchronous parts are kept where they belong, and I want the type system's help in doing that. "May perform asynchronous operations" is a property a calling function inherits from its callee and it is correctly modelled as such. I don't want to call functions that I don't know this about.

Now you can make an argument that you don't want to design your code this way and that's great if you have another way to think about it all that leads to code that can be maintained and reasoned about equally well (or more so). But calling the classes of functions red and blue and pretending the distinction has no more meaning than that is not such an argument. It's empty nonsense.

"We" don't all agree on this.


> The fact that a function can perform asynchronous operations matters to me and I want it reflected in the type system.

async doesn't tell you whether the function performs asynchronous operations, despite the name. async is an implementation detail about how the function must be invoked.

As TFA correctly points out, there's nothing stopping you from calling a blocking function inside a future, and blocking the whole runtime thread.


I didn't say it tells me whether the function does perform such operations, I said it tells me it can. More importantly it tells me which functions (most) can't.


It would be swell if functions could be generic over this capability at compile time, so that you could get the same guarantees from the type system without implementing the same protocols more than one time.


Haskell supports this, but right from the start Rust was always wary of trying to add higher kinded types, which are necessary to support this.


As a Zig programmer I also get to enjoy this, but from the angle of language implementors not caring about type theory


Keyword generics!


Maybe a better example is returning errors, than const.

Either way, all of these changes are really annoying to make. We want less of these annoyances, not more.


We do not want functions that take floating point arguments, only u32 should be used. And don't get me started on more than one argument!


you can convert a float to a u32.

you cannot convert an function that calls async code into a sync function.


An async function is some syntactic sugar around a sync function that returns a future. You can merrily call one from the other.

You can only convert an int to a float with significant caveats. It's not a general trivial conversion. More complicated types may not be convertible at all or behave in all sorts of exciting ways (including having arbitrary side effects).

The point is that none of that is different to async functions. Of course you have to know what to do with them for them to be useful, but there is no requirement for them to "infect" calling code.


You can call .Wait on the Task it returns :)


Right, but now you are forced to convert the calling function to async.

u32 / float does not have the problem. It does not "bubble up", unless you want it to.


no, .Wait in C# or block_on in Rust keep the caller sync while evaluating the async callee, preventing the "bubble up".


Virtual threading is fun and all until you find out SimpleDateFormat and a bunch of other classes built tight into your standard library aren't thread safe and now you need to go through your program and find out what else you missed. Go too has these fancy green threads at the cost of manually locking resources and finding out about race conditions when you forget about them.

Futures aren't a fundamental CS mistake, they're a design decision. You may disagree with that decision, but the advantage Rust brings is that you don't need to worry about thread safety once your program actually compiles, at the cost of different code styles.

Neither asynchronous processing design is fundamentally wrong, they both have their strengths and weaknesses.


> Virtual threading is fun and all until you find out SimpleDateFormat and a bunch of other classes built tight into your standard library aren't thread safe and now you need to go through your program and find out what else you missed. Go too has these fancy green threads at the cost of manually locking resources and finding out about race conditions when you forget about them.

Why would that ever be an issue? Instances of those classes shouldn't be shared between virtual threads just the same as when using regular threads.


> until you find out SimpleDateFormat and a bunch of other classes built tight into your standard library aren't thread safe

true, but DateTimeFormatter has been available since Java 8, released almost 10 years ago.

VirtualThreads will be available in Java tomorrow

Also: https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDa...


I want colored functions. I want to know which code is running synchronously and which doesn't, which raises errors and which doesn't. Color is just a description of the function's properties (and effects) and how it's compatible with other colors.

There is also nothing fundamentally bad with cooperative scheduling in scope of a single process.


I got into programming in the 1990s. At that point in time, there was still a large contingent of programmers loudly insisting they needed assembly language to do everything. And to be clear, I mean, everything. Not "Yeah, I can't really bring up an OS without a bit of specialized assembly" but "every programmer should write every program in assembly".

The vast majority of them were already wrong. They only got more wrong.

You may just be used to knowing what code is "synchronous" and what isn't because it's been shoved into your face and you've adapted your thought process to it. In practice, "everything important is doing something 'asynchronously'" turns out to be the vast majority of what you need, and the vast majority of your mental energy you are dedicated to splitting the world in two is a waste. For the little bit that remains, by all means use something specialized, but it's just not something that everyone, everywhere, needs to be doing all the time, any more than everyone everywhere should be manually allocating registers, or any more than programs need to have line numbers because otherwise how can they work? (One of my favorites because I remember having that conception myself.)


I think you have your analogy backwards: the "assembly programmer" in this situation is the person who doesn't understand why one would "color" functions and/or express a fundamental property as part of their types. "Why do we need to express this in their type? Every programmer should be able to understand this without help".


To me this kind of sounds like circular reasoning. Without function coloring there's no distinction that you need to know of.

Can you elaborate?


> Without function coloring there's no distinction that you need to know of

Why do you think you don't need to know of it? I want to know if the function I'm calling is going to make a network request. Just because I can have a programming language that hides that distinction from me doesn't mean I want that.

Ideally I want to have the fundamental behavior of any function I call encoded in the function signature. So if it's async, I know it's going to reach out to some external system for some period of time.


> I want to know if the function I'm calling is going to make a network request.

That has nothing to do with function coloring.

> Ideally I want to have the fundamental behavior of any function I call encoded in the function signature.

There is no distinction of async functions if you don't have function coloring that you can encode in type signatures.


> That has nothing to do with function coloring.

Sure, in the same way that types have nothing to do with enforcing logical correctness of software.

> There is no distinction of async functions if you don't have function coloring that you can encode in type signatures.

What are you trying to say with this statement?


getaddrinfo() is a synchronous function that can do network requests to resolve DNS. The network property isn't reflected in its function signature becoming async. You can have an async_getaddrinfo() which does, but the former is just a practical example of network calls in particular being unrelated to function coloring.


It's nonsense. Async in rust is just syntactic sugar around a function signature. You can merrily call async functions from sync rust, you just have to know what to do with the future you get back.


"you just have to know what to do" is the problem. You can call any color from any color, but for some colors it's trivial, e.g. sync function from a sync or async one, or a non-failing function from a failing or non-failing one.

I don't want to be able to call fallible function from an infallible one trivially, I want the compiler to force me to specify what exactly I'm going to do with an error if it happens. Likewise for async-from-sync: there are many ways I could call these: I can either create a single threaded executor and use it to complete the future to completion, or maybe I want to create a multithreaded executor, or maybe I expect the future complete in a single poll and never suspend and I don't even need a scheduler.


Well yes to all that. I still don't see the problem. An async function isn't really an async function, it's a sync function that returns a future. Would it be better if all that was manual? I've done quite a bit of stuff using manual async traits and it's painful and I highly value the syntax sugar that async brings. That said, I certainly don't want some executor running quietly behind the scenes doing async stuff for me without my explicit and full control. If I want to manually poll a future, that's for me to decide.


You seem to raise valid points and I don't disagree with you, however I don't see how it's relevant to the original concern regarding colored functions.


I suppose I'm struggling to understand what "colour" means in the context of Rust. It's surely just another word for signature. For some reason it's trotted out every time there's a discussion about async. I can only assume it's to do with the original use of the term for JavaScript async (which I know almost nothing about and have no opinion on), but I just cannot see its point in Rust async.


It has to do with the fact that most of the code in the project is not async but having to call async functions often propagates all the way to your main function. It's infectious and many people don't like it, myself included, that's why I'm working with Elixir and Golang where async is transparent and 99% automatic, or explicit but non-infectious, respectively.

I do love Rust and found a number of very valid uses for it but its async story leaves a lot to be desired. I don't enjoy writing it though I do enjoy the results.


This classic article mixes two things:

1. inability to read an async result from a sync function, which is a legitimately major architectural limitation.

2. author's opinion how function syntax should look like (fully implicit, hiding how the functions are run).

And from this there is the endless confusion and drama.

The problem 1 is mostly limited to JS. Languages that have threads can "change colour" of their functions at will, so they don't suffer from the dramatic problem described in the article.

But people see languages don't fit the opinion 2, of having magic implicit syntax, and treat it as an equally big deal the dead-end problem 1. But two syntaxes are somewhere between minor inconvenience to actual feature. In systems programming it's very important which type of locks you use, so you really need to know what runs async.


Maybe the issue is that we overload the concept of a function with an entirely different thing, a Future / Promise. Maybe if the syntax would have been entirely different too, it would have been easier to understand. We tend to have different syntax for different things.

I’m hesitant towards not distinguishing different things anymore and let the underlying system “figure it out”. I’m sure this could work as long as you’re on the happy path, but that’s not the only path there is.


I think with stackful coroutines you lose low-overhead interoperability with C. Also, it possible to use stackless coroutines without introducing async/await 'colors'.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: