Beware, the following is a long-winded reflection on the current discussion on async and IO abstraction. There is no purpose to it but to organize my thoughts and comes from someone with limited understanding of what is being achieved.
In his last live stream, Andrew K. shared his reflection (intention?) to start working on async/await again and started introducing a new concept: the IO interface.
My understanding of it is this Io
interface is an inversion of control. It’s an abstraction that would be injected to any function that wants to do IO: read/writing a file, sending/receiving on the network, etc. This interface would be similar to the Allocator
interface in principle and would allow to abstract any IO operation so that an implementation could be plugged in independently of whatever your trying to do with this Io
.
(We will set aside the complexity of implementing an Io
abstraction that would satisfy all the constraints that any IO might require and trust the zig core team to figure out those pesky details. )
Andrew gave the example of a jpeg library that read and write jpeg files. You want the library to provide the ability to access jpeg files but in the same way you don’t want the library to impose on you a way to allocate memory, you don’t want this library to impose on you a way to do Io
. Is this going to be a file on a disk, or is it going to be buffers in memory? Are the IOs going to be handled in a mono-threaded event-loop or is it going to be managed by a thread pool? etc. This will depend on the implementation of this Io
interface that the consuming final program can choose depending on its requirements.
With this idea, the jpeg library is abstracted away from those concerns and can just rely on a standard interface to read and write “stuff” from some source or sink. This goes beyond async/await in reality and can be used to solve all kind of problems. Is your OS posix or not? You don’t really care anymore. Want to intercept an IO done so you can debug or compute some statistics? Add a layer in the IO abstraction, etc.
In my view, introducing this Io
abstraction would be getting zig closer to what developing JS in a browser looks like. Where any IO is handled by your runtime and all you need to do is “await” it. The difference is that, instead of being a feature of the language itself, with keywords and function coloring and whatnot, it’s just an API in the Io
interface.
There is a notion of stackful, stackless and green thread that popped up in the discussion but that’s for people interested in language design. A smolbrain like me is only interested in the impact from a zig developer perspective.
As a developer, I often agonize about how to design my APIs and most of the time this agonizing is really about IO. The most general way of dealing with this is indeed the reader/writer interface which I don’t really like using. The fact that you have to specify an anytype
and just expect the user to know what you want is really not great. People have rationalized it in many ways in this forum but the lack of comptime interface in zig just makes this clunky. Most of the time I need to “parse” a file (or decode it) I just end up taking a slice containing the entire file and returning some sort of struct referencing data into that slice so my function is not involved in doing any IO or even in doing any allocation if the file format allows to upper bound the data structures.
However, coming back to the async/await subject, it seems to me that the Io
abstraction is just going to solve one side of the async
coin. The Io
contention side. The other use of async
is for CPU contention. When you really want to multi-thread your code because some function is CPU bound and you want your other cores to do stuff in the meantime. I fail to see how this can be generalized or maybe I misunderstand Andrew’s intention. Maybe that is a completely different problem that should be handled separately in a different leg of the async
story?