Live data from Hacker News

Zig's New Async I/O

kristoff.it

201–210 of 293 posts

Re: Zig's New Async I/O

#201

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…

That paper (P1364R0) was contentious, and I regard it as being severely motivated reasoning, published only to kill off competing approaches to C++ coroutines.

Some discussions of that paper:

* https://old.reddit.com/r/cpp/comments/1jwlur9/stackful_corou...

* https://old.reddit.com/r/programming/comments/dgfxde/fibers_...

Re: Zig's New Async I/O

#202
post #3

I feel that I have to point this out once again, because the article goes so far as to state that: > With this last improvement Zig has completely defeated function coloring. I disagree with this. Let's look at the 5 rules referenced in the famous "What color is your function?" article referenced here. > 1. Every function has a color Well, you don't have async/sync/red/blue anymore, but you now have IO and non-IO fun…

I'll let a real category theorist get into the details that I'll likely flub, but the IO monad is where you end up if you start on this path. That context can be implicit, but it's there, and if you want any help from the compiler (to, for example, guide Claude Code towards useful outcomes) you've got to reify it as a real thing in the formality of the system. Async and coroutines are the graveyard of dreams for syst…

it's not a monad, since you can do unholy (for fp) things with it, like stash it in a struct and pass the struct around (even to functions which have no clue theres an io call) or just grab a globalized io object and use that at will arbitrarily at many entrypoints in your function.

most importantly, besides the obvious situations (creating the io object, binding it to another object), it's not generally going to be returned from a function as part of its value.

Re: Zig's New Async I/O

#203

Earlier quoted context omitted.

except you cant "pass the same event loop in multiple locations". its also not an easy lift. the zig std will provide a few standard implementations which would be trivial to drop in.

I thought it was the exact same; an event loop in Python is just whatever Io is in Zig, make it a param, get it from an import and a lookup (`import asyncio; loop = asyncio.get_running_loop()`). I might be misunderstanding what you're saying though.

[deleted]

Re: Zig's New Async I/O

#204

Earlier quoted context omitted.

except you cant "pass the same event loop in multiple locations". its also not an easy lift. the zig std will provide a few standard implementations which would be trivial to drop in.

I thought it was the exact same; an event loop in Python is just whatever Io is in Zig, make it a param, get it from an import and a lookup (`import asyncio; loop = asyncio.get_running_loop()`). I might be misunderstanding what you're saying though.

hm maybe. i guess ive only used python in situations where it injects it into amain so i could have been confused. i thought python async was a wrapper around generators, and so the mechanism cant be instantiated in multiple places. i stand corrected.

Re: Zig's New Async I/O

#205
post #76

Earlier quoted context omitted.

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

one of the io interfaces provided is a standard threadpool io. and if it was really important, you could write your own io interface that selects between std threadpool and std blocking based off of an option (i am guessing, i don't know, but seems reasonable)

Re: Zig's New Async I/O

#206

Earlier quoted context omitted.

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

and yet... there are libraries that were written twice, once for async and once for not.

Re: Zig's New Async I/O

#207
post #108

Earlier quoted context omitted.

By CPS do you mean lightweight threads + meeting point channels? (i.e. both the reader and writer get blocked until they meet at the read/write call) Or something else? Why is CPS better and lower level than async/await?

Async/await tend to be IO bound, in zigs case hiw can i know what to use and when? Say i do a db call, thats clearly IO, and later do heavy CPU thing, now how do i know that the CPU thing does not block inside my async io thing? I guess its pure luck if the io implementation can handle both, but who knows?

> how do i know that the CPU thing does not block inside my async io thing?

I think it's common sense to not interweave IO with long-running CPU, hence sans IO.

If you want to go that route, we already have solutions: goroutines and beam processes.

Re: Zig's New Async I/O

#208
post #192

I think this design is a regression from the previous design, in which you could use compile time introspection to check whether things are actually async (calling convention) or not. Additionally, I don't necessarily want to delegate the management of the memory backing the futures to an Io, or pass around a blob of syscalls and an associated runtime, which accesses everything via a vtable. I would prefer to have th…

Your preference to have them be compile time generic shouldn’t come at the cost of those that would want runtime virtualisation. As the article concludes, you get the best of both worlds here, where the result is effectively compile time generic if you only use one io implementation in your program. In theory it’d also partially compile time generic if you exclusively use one io for one set of libraries/functions and…

Yes, I understand that the designers prefer the Allocator situation and that Reader and Writer being anytype was downstream of the difficulty of using async readers and writers otherwise. So the intention was always to go with the design that I do not prefer. One reason I do not prefer it is that the Reader and Writer interfaces were already staggeringly inefficient, despite the lack or virtualization. We have avoided the issue by reimplementing a bunch of their API in some specific readers and writers and modifying the stdlib Reader and Writer to dispatch to these methods if they are present.

To be honest, I just do not have much faith in the commitment to optimality, when it seems like the team has not spent time doing things like profiling a program that spends a lot of time formatting integers as decimal syrings, and noticing that the vast majority of that formatting runtime is UTF-8 validation. I am happy to continue using the language, because it makes it easy enough to fix these issues oneself.

The only aspect that may not be recoverable by the end user is the "am I async/is this async" reflection issue, though a core team member has clarified in this comment section that the code in the article is a sketch and the design of stackless coroutines is far from done, so we may yet get this.

Some other philosophical point is, like, lua's coroutine.create/resume/yield/clone are control flow primitives for use within a single thread of execution. It's fine to ship an async runtime, which embodies the view they they are not control flow primitives for use within a single thread of execution, for doing I/O. But focusing the primitives for creating and switching between execution contexts too narrowly on the async runtime use case is liable to he harmful to other use cases for these operations. Ideally, we would be able to write things like a prominent SNES emulator that uses stack switching to ensure the simulation of different components proceeds in an order known to be more correct than other orders, and we would be able to do it using native language features, which would compile down to something a bit cheaper than dumping all of our registers onto the stack. Ideally when we do this we would not be asked by the language to consider what it would mean to "cancel" the execution context managing one of the components, in the same way that we do not need to consider what it means to cancel an arbitrary struct, or the function which is calling the function currently executing.

Re: Zig's New Async I/O

#209

Earlier quoted context omitted.

I'll let a real category theorist get into the details that I'll likely flub, but the IO monad is where you end up if you start on this path. That context can be implicit, but it's there, and if you want any help from the compiler (to, for example, guide Claude Code towards useful outcomes) you've got to reify it as a real thing in the formality of the system. Async and coroutines are the graveyard of dreams for syst…

it's not a monad, since you can do unholy (for fp) things with it, like stash it in a struct and pass the struct around (even to functions which have no clue theres an io call) or just grab a globalized io object and use that at will arbitrarily at many entrypoints in your function. most importantly, besides the obvious situations (creating the io object, binding it to another object), it's not generally going to be…

i'm relatively confident that andrew will happily break the language again (god love him for that) when it becomes clear that you really want that algebra.

though i will say, for a systems language, it's probably better to invert the lift/unlift relationship, default to do-notation and explicitly unlift into pure functions. that's almost what const meant in C++ to begin with but it lost it's way.

Re: Zig's New Async I/O

#210

Ok, they are implementing an effect system. Is there any acknowledgement that they are going down an established path?

When I watched the release notes they didn't sound like it was some ground breaking new pattern it's just a new approach that fits best with zig.
Post reply on HN