Hacker News new | ask | show | jobs
by ancarda 229 days ago
I really hope this is going to be entirely optional, but I know realistically it just won't be. If Rust is any example, a language that has optional async support, async will permeate into the whole ecosystem. That's to be expected with colored functions. The stdlib isn't too bad but last time I checked a lot of crates.io is filled with async functions for stuff that doesn't actually block.

Async clearly works for many people, I do fully understand people who can't get their heads around threads and prefer async. It's wonderful that there's a pattern people can use to be productive!

For whatever reason, async just doesn't work for me. I don't feel comfortable using it and at this point I've been trying on and off for probably 10+ years now. Maybe it's never going to happen. I'm much more comfortable with threads, mutex locks, channels, Erlang style concurrency, nurseries -- literally ANYTHING but async. All of those are very understandable to me and I've built production systems with all of those.

I hope when Zig reaches 1.0 I'll be able to use it. I started learning it earlier this month and it's been really enjoyable to use.

10 comments

> I do fully understand people who can't get their heads around threads and prefer async.

Those are independent of each other. You can have async with and without threads. You can have threads with and without async.

Yeah, it always mystifies me when people talking about async vs threads when they are completely orthogonal concepts. It doesn't give me the feeling they understand what they are talking about.
It's because JavaScript was the thing that popularized async/await for many developers, and they aren't aware that it originated in a language (C#) and runtime (.NET) that are fully multithreaded.
It's optional to the point that you can write single-threaded version without any io.async/io.concurrent, but you will need to pass the io parameter around, if you want to do I/O. You are mistaking what is called "async" here for what other languages call async/await. It's a very different concept. Async in this context means just "spawn this function in a background, but if you can't, just run it right now".
> I'm much more comfortable with threads

The example code shown in the first few minutes of the video is actually using regular OS threads for running the async code ;)

The whole thing is quite similar to the Zig allocator philosophy. Just like an application already picks a root allocator to pass down into libraries, it now also picks an IO implementation and passes it down. A library in turn doesn't care about how async is implemented by the IO system, it just calls into the IO implementation it got handed from the application.

or you can pick one manually. Or, you can pick more than one and use as needed in different parts of your application. (probably less of a thing for IO than allocator).
You don’t have to hope. Avoiding function colours and being able to write libraries that are agnostic to whether the IO is async or not is one of the top priorities of this new IO implementation.

If you don’t want to use async/await just don’t call functions through io.async.

> can't get their heads around threads and prefer async

Wow. Do you expect anyone to continue reading after a comment like that?

I understand threads but I like using async for certain things.

If I had a web service using threads, would I map each request to one thread in a thread pool? It seems like a waste of OS resources when the IO multiplexing can be done without OS threads.

> last time I checked a lot of crates.io is filled with async functions for stuff that doesn't actually block.

Like what? Even file I/O blocks for large files on slow devices, so something like async tarball handling has a use case.

It's best to write in the sans-IO style and then your threading or async can be a thin layer on top that drives a dumb state machine. But in practice I find that passable sans-IO code is harder to write than passable async. It makes a lot of sense for a deep indirect dependency like an HTTP library, but less sense for an app

same. i don't like async. i don't like having to prepend "await" to every line of code. instead, lately (in js), i've been playing more with worker threads, message passing, and the "atomics" api. i get the benefits of concurrency without the extra async/await-everywhere baggage.
lol it’s just a very different tradeoff. Especially in js those approaches are far more of a “baggage” than async/await
probably, but i'm petty like that. i just really don't like async/await. i'm looking forward to eventually being punished for that opinion!
I agree, async is way more popular than it should be. At least (AFAIU) Zig doesn't color functions so there won't be a big ecosystem rift between blocking and async libraries.
> I do fully understand people who can't get their heads around threads and prefer async

This is a bizarre remark

Async/await isn't "for when you can't get your head around threads", it's a completely orthogonal concept

Case in point: javascript has async/await, but everything is singlethreaded, there is no parallelism

Async/await is basically just coroutines/generators underneath.

Phrasing async as 'for people who can't get their heads around threads' makes it sound like you're just insecure that you never learned how async works yet, and instead of just sitting down + learning it you would rather compensate

Async is probably a more complex model than threads/fibers for expressing concurrency. It's fine to say that, it's fine to not have learned it if that works for you, but it's silly to put one above the other as if understanding threads makes async/await irrelevant

> The stdlib isn't too bad but last time I checked a lot of crates.io is filled with async functions for stuff that doesn't actually block

Can you provide an example? I haven't found that to be the case last time I used rust, but I don't use rust a great deal anymore

> This is a bizarre remark

> makes it sound like you're just insecure

> instead of just sitting down + learning it you would rather compensate

Can you please edit out swipes like these from your HN posts? This is in the site guidelines: https://news.ycombinator.com/newsguidelines.html.

Your comment would be fine without those bits.

>Case in point: javascript has async/await, but everything is singlethreaded, there is no parallelism, Async/await is basically just coroutines/generators underneath.

May be I just wish Zig dont call it async and use a different name.

It's more about how the code ends up evolving.

Async-await in JS is sometimes used to swallow exceptions. It's very often used to do 1 thing at a time when N things could be done instead. It serializes the execution a lot when it could be concurrent.

    if (await is_something_true()) {
      // here is_something_true() can be false
    }
And above, the most common mistake.

Similar side-effects happen in other languages that have async-await sugar.

It smells as bad as the Zig file interface with intermediate buffers reading/writing to OS buffers until everything is a buffer 10 steps below.

It's fun for small programs but you really have to be very strict to not have it go wrong (performance, correctness).

I think you replied to the wrong person.

That being said, I don't understand your `is_something_true` example.

> It's very often used to do 1 thing at a time when N things could be done instead

That's true, but I don't think e.g. fibres fare any better here. I would say that expressing that type of parallel execution is much more convenient with async/await and Promise.all() or whatever alternative, compared to e.g. raw promises or fibres.

I replied directly to you. There are valid arguments as to why async-await sucks, even after you're deeply familiar with how it works. Even though it's just generators/coroutines beneath, async-await pollutes the code completely if you're not strict about its usage.

`is_something_true` is very simple, if condition is true, and then inside the block, if you were to check again it can be false, something that can't happen in synchronous code, yet now, with async-await, it's very easy to get yourself into situations like these, even though the code seems to yell at you that you're in the true branch. the solution is adding a lock, but with such ease to write async-await, it's rarely caught

> I replied directly to you

My comment was responding only to the person who equated threads and async. My comment only said that async and threading are completely orthogonal, even though they are often conflated

> `is_something_true` is very simple, if condition is true, and then inside the block, if you were to check again it can be false, something that can't happen in synchronous code

It can happen in synchronous code, but even if it couldn't - why is async/await the problem here? what is your alternative to async/await to express concurrency?

Here are the ways it can happen:

1. it can happen with fibers, coroutines, threads, callbacks, promises, any other expression of concurrency (parallel or not!). I don't understand why async/await specifically is to blame here.

2. Even without concurrency, you can mutate state to make the value of is_something_true() change.

3. is_something_true might be a blocking call to some OS resource, file, etc - e.g. the classic `if (file_exists(f)) open(f)` bug.

I am neutral on async/await, but your example isn't a very good argument against it

Seemingly nobody ever has any good arguments against it

> async-await pollutes the code completely if you're not strict about its usage

This is a good thing, if a function is async then it does something that won't complete after the function call. I don't understand this argument about 'coloured functions' polluting code. if a function at the bottom of your callstack needs to do something and wait on it, then you need to wait on it for all functions above.

If the alternative is just 'spin up an OS thread' or 'spin up a fiber' so that the function at the bottom of the callstack can block - that's exactly the same as before, you're just lying to yourself about your code. Guess what - you can achieve the same thing by putting 'await' before every function call

Perhaps you have convinced me that async/await is great after all!

Async and threading are applied to similar problems, that's how I understood OP.

`if (file_exists(f))` is misuse of the interface, a lesson in interface design, and a faulty pattern that's easy to repeat with async-await.

> I don't understand this argument about 'coloured functions' polluting code.

Let's say you have some state that needs to be available for the program. What happens in the end game is that you've completely unrolled the loads and computation because any previous interface sucks (loading the whole state at the beginning which serializes your program and makes it slower, or defining `async loadPartial` that causes massive async-await pollution at places where you want to read a part of the state, even if it is already in cache).

Think about it, you can't `await` on part of the state only once and then know it's available in other parts of code to avoid async pollution. When you solve this problem, you realize `await` was just in the way and is completely useless and code looks exactly like a callback or any other more primitive mechanism.

A different example is writing a handler of 1 HTTP request. People do it all the time with async-await, but what to do when you receive N HTTP requests? The way to make code perform well is impossible with the serial code that deals with 1 request, so async-await just allowed you to make something very simple and in an easy way, but then it falls apart completely when you go from 1 to N. Pipelining system won't really care for async-await at all, even though it pipelines IO in addition to compute.

I think "how to express concurrency" is a question I'm not even trying to answer, although I could point to approaches that completely eliminate pollution and force you to write code in that "unrolled" way from start, something like Rx or FRP where time is exactly the unit they're dealing with.

It sounds like your trouble with async is mistaking concurrency for parallelism.
Weird claim since threads were originally introduced as a concurrency primitive, basically a way to make user facing programs more responsive while sharing the same address space and CPU.

The idea of generalizing threads for use in parallel computing/SMP didn't come until at least a decade after the introduction of threads for use as a concurrency tool.

> Weird claim since threads were originally introduced as a concurrency primitive

Wasn't this only true when CPUs were single core only? Only when multi core CPUs came, true parallelism could happen (outside of using multiple CPUs)