Live data from Hacker News

Zig's New Async I/O

kristoff.it

71–80 of 293 posts

Re: Zig's New Async I/O

#71
As the author of a semi-famous post about how Zig has function colors [1], I decided to read up on this.

I see that blocking I/O is an option:

> The most basic implementation of `Io` is one that maps to blocking I/O operations.

So far, so good, but blocking I/O is not async.

There is a thread pool that uses blocking I/O. Still good so far, but blocking I/O is still not async.

Then there's green threads:

> This implementation uses `io_uring` on Linux and similar APIs on other OSs for performing I/O combined with a thread pool. The key difference is that in this implementation OS threads will juggle multiple async tasks in the form of green threads.

Okay, they went the Go route on this one. Still (sort of) not async, but there is an important limitation:

> This implementation requires having the ability to perform stack swapping on the target platform, meaning that it will not support WASM, for example.

But still no function colors, right?

Unfortunately not:

> This implementation [stackless coroutines] won’t be available immediately like the previous ones because it depends on reintroducing a special function calling convention and rewriting function bodies into state machines that don’t require an explicit stack to run.

(Emphasis added.)

And the function colors appear again.

Now, to be fair, since there are multiple implementation options, you can avoid function colors, especially since `Io` is a value. But those options are either:

* Use blocking I/O.

* Use threads with blocking I/O.

* Use green threads, which Rust removed [2] for good reasons [3]. It only works in Go because of the garbage collector.

In short, the real options are:

* Block (not async).

* Use green threads (with their problems).

* Function colors.

It doesn't appear that the function colors problem has been defeated. Also, it appears to me that the Zig team decided to have every concurrency technique in the hope that it would appear innovative.

[1]: https://gavinhoward.com/2022/04/i-believe-zig-has-function-c...

[2]: https://github.com/aturon/rfcs/blob/remove-runtime/active/00...

[3]: https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p13...

Re: Zig's New Async I/O

#72

Earlier quoted context omitted.

Sure it is a function coloring. Just in a different form. `async` in other languages is something like an implicit parameter. In zig they made this implicit parameter explicit. Is that more better/more ergonomic? I don't know yet. The sugar is different, but the end result the same. Unless you can show me concrete example of things that the approach zig has taken can do that is not possible in say, rust. Than I don't…

> Unless you can show me concrete example add io to a struct and let the struct keep track of its own io.

Unless I'm misunderstanding, that's effectively implementing Future for the struct

Re: Zig's New Async I/O

#73
Although I'm not wild about the new `io` parameter popping up everywhere, I love the fact that it allows multiple implementations (thread based, fiber based, etc.) and avoids forcing the user to know and/or care about the implementation, much like the Allocator interface.

Overall, I think it's a win. Especially if there is a stdlib implementation that is a no overhead, bogstock, synchronous, blocking io implementation. It follows the "don't pay for things you don't use" attitude of the rest of zig.

Re: Zig's New Async I/O

#74
post #19

Earlier quoted context omitted.

To me, there's no difference between the IO param and async/await. Adding either one causes it to not be callable from certain places. As for the second thing: You can do that, but... You can also do this in Rust. Yet nobody would say Rust has solved function coloring. Also, check this part of the article: > In the less common case when a program instantiates more than one Io implementation, virtual calls done throug…

>To me, there's no difference between the IO param and async/await. You can't pass around "async/await" as a value attached to another object. You can do that with the IO param. That is very different.

> You can't pass around "async/await" as a value attached to another object

Sure you can? You can just pass e.g. a Task around in C# without awaiting it, it's when you need a result from a task that you must await it.

Re: Zig's New Async I/O

#75

I'm generally a fan of Zig, but it's a little sad seeing them go all in on green threads (aka fibers, aka stackful coroutines). Rust got rid of their Runtime trait (the rough equivalent of Zig's Io) before 1.0 because it performed badly. Languages and OS's have had to learn this lesson the hard way over and over again: https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p13... > While fibers may have looked like…

I’m confused about the assertion that green threads perform badly. 3 of the top platforms for high concurrency servers use or plan to use green threads (Go, Erlang, Java). My understanding was that green threads have limitations with C FFI which is why lower level languages don’t use them (Rust). Rust may also have performance concerns since it has other constraints to deal with.

Re: Zig's New Async I/O

#76

Earlier quoted context omitted.

Wouldn't this only work if there's only one implementation throughout the entire compliation unit? If you use 2 allocators in your app, your restricted function type has 2 possible callees for each entry, and you're back to the same problem.

> Wouldn't this only work if there's only one implementation throughout the entire compliation unit in practice how often are people using more than one io in a program?

I think having a thread pool on top of some evented IO isn't _that_ uncommon.

You might have a thread pool doing some very specific thing. You can do your own threadpool which wont use the Io interface. But if one of the tasks in the threadpool wanted to read a file, I guess you'd have to pass in the blocking Io implementation.

Re: Zig's New Async I/O

#77
post #37

Earlier quoted context omitted.

I'm aware, but Zig isn't a democracy where the core team votes, right? Has Andrew actually expressed that he wants the proposal? Without that we're left with scraps like this commit message where he seems ambivalent. https://github.com/ziglang/zig/commit/d6c90ceb04f8eda7c6b711... Andrew, I know you read these threads sometimes, give us a sign so I can go down the mountain with my stone tablets and tell the people whe…

We don't know whether or not we'll have stackless coroutines; it's possible that we hit design problems we didn't foresee. However, at this moment, the general consensus is that we are interested in pursuing stackless coroutines. While Andrew has the final say, as Loris points out, we always work to reach a consensus internally. The article lists this an an implementation that will probably exist, because we agree th…

But how will that actually work? Your stackless coroutines proposal talks about explicit primitives for defining a coroutine. But what about a function that's not designed for any particular implementation strategy - it just takes an Io and passes it on to some other functions? Will the compiler have a way to compile it as either sync or async, like apparently it did before? It would have to, if you want to avoid function colors. But your proposal doesn't explain anything about that.

Disclaimer: I'm not actually a Zig user, but I am very interested in the design space.

Re: Zig's New Async I/O

#78
post #69
post #48

Earlier quoted context omitted.

You are expecting them to actually check the value, there is nothing preemptive. Another approach is special messages over a side channel.

So instead of a context you need to pass a a channel. Same problem.

Not necessarily, that is one of the reasons OOP exists.

Have a struct representing the set of associated activities, owning the channel.

Re: Zig's New Async I/O

#79
post #24

Earlier quoted context omitted.

> It's quite rare for a function to unexpectedly gain a dependency on ... If this was true in general, the function coloring problem wouldn't be talked about. However, the second point is more interesting. I think there's a bit of a Stockholm syndrome thing here with Zig programmers and Allocator. It's likely that Zig programmers won't mind passing around an extra param. If anything, it would make sense to me to have…

> But I guess it's going to be 2 params from now on. >> So, if you discover that a code path you previously thought was pure actually does need to perform IO, then you don't need to apply some nasty viral change; you just grab `my_thing.io

Python, for example, will let you call async functions inside non-async functions, you just have to set up the event loop yourself. This isn't conceptually different than the Io thing here.

Re: Zig's New Async I/O

#80
post #19

Earlier quoted context omitted.

To me, there's no difference between the IO param and async/await. Adding either one causes it to not be callable from certain places. As for the second thing: You can do that, but... You can also do this in Rust. Yet nobody would say Rust has solved function coloring. Also, check this part of the article: > In the less common case when a program instantiates more than one Io implementation, virtual calls done throug…

> Adding either one causes it to not be callable from certain places. you can call a function that requires an io parameter from a function that doesn't have one by passing in a global io instance? as a trivial example the fn main entrypoint in zig will never take an io parameter... how do you suppose you'd bootstrap the io parameter that you'd eventually need. this is unlike other languages where main might or might…

You can call an async function from a function that is not async by passing in a global runtime (/ event loop).

As a trivial example the main entry point in rust is never async. How’d you suppose you’d bootstrap the runtime that you’d eventually need.

This is pretty much like every other langage.

Post reply on HN