If you haven't tried Zig since 0.16.0 was released, I highly recommend having a look. The release notes for this release were huge!!
If you haven't tried Zig since 0.16.0 was released, I highly recommend having a look. The release notes for this release were huge!!
the upcoming releases are expected to provide a solution to this “dispatch is comptime-known, but still dynamic” problem, and drop the loses in efficiency.
That is, I think the point was DevX not io performance.
It's one of these things where it's extremely use case dependant - like many performance issues, you probably don't care about it - but when you do it matters.
i dont think you need to specify that. the compiler can figure it out and do an optimization pass at the end.
I had some trouble understanding how the async/await mechanism works:
var foo_future = io.async(foo, .{args});
defer if (foo_future.cancel(io)) |resource| resource.deinit() else |_| {}
var bar_future = io.async(bar, .{args});
defer if (bar_future.cancel(io)) |resource| resource.deinit() else |_| {}
const foo_result = try foo_future.await(io);
const bar_result = try bar_future.await(io);
My assumption is that calling io.async using an event loop implementation of IO, it will internally start a "task" (or whatever it should be called) and that the future is a handle to it. So far so good.The part that I don't understand is what happens when you call future.await(io). Will the IO implementation somehow suspend the current function and resume once the future is resolved? If so, does that mean that every function in zig is a stackless coroutine?
No and yes.
If you're using Io.Threaded, then the concurrency model is multithreading and calling Future.await will block your thread on a OS futex.
If you're using Io.Evented, then the concurrency model is green threads / fibers and calling Future.await will suspend the current green thread by yielding (swapping CPU state with another fiber).
Zig currently does not support stackless coroutines so today you can't have that, but we used to have them (pre self-hosted compiler), and there's an accepted proposal to bring them back, in which case any function that calls await, or that otherwise has a suspension point, would have to be transformed into a stackless coroutine by the compiler, yes. The plan is for that to happen transparently without requiring an `async` annotation in the function signature, like we already did in the past.
This is an old post of mine that explains how that worked at a high level: https://kristoff.it/blog/zig-colorblind-async-await/
If the compiler will treat functions that call a library function (Future.await) as special and change how the call site is compiled, why not just have an `await` keyword that when present will convert it into a state machine that can be suspended/resumed?
In other words: What is gained by not having a keyword that changes how a function is emitted if the compiler will change it anyway based on detection of a library call?
`suspend` is the keyword that best fits your description (you can think of it as being closely related to `yield` in other languages), and it's kind of a lower-level primitive compared to async/await.
This also means that, according to our plans, Zig will have to propagate "stackless-ness" upwards in the call chain while analyzing the code (thus making Future.await not special per-se).
Very interesting, will be following Zig development more closely. Thanks for sharing!