4 ms·
Zig’s functions are colorblind: https://kristoff.it/blog/zig-colorblind-async-await/ https://kristoff.it/blog/zig-colorblind-async-await/
by jedisct1 5y ago
Zig’s functions are colorblind: https://kristoff.it/blog/zig-colorblind-async-await/ https://kristoff.it/blog/zig-colorblind-async-await/
- BiteCode_dev 5y agoThat's an interesting design, it seems like the best of both worlds. Zip has really neat ideas. I wish it didn't go the "case as you want road" though.
- messe 5y ago> I wish it didn't go the "case as you want road" though. That's almost inevitable given its desire for close interop with C (as in, the Zig compiler includes a C compiler and can directly #include C headers). The standard library has a relatively consistent case style though, which the Zig guide recommends.
- daurnimator 5y agoIndeed, partially thanks to this article! It was the key piece of material that helped me convince Andrew to make zig's async colour-less.
- meowface 5y agoThe internet is pretty cool.
- XorNot 5y agoThis doesn't seem as useful as it implies though with the proviso statement that you can wind up with deadlocks in sync mode that you wouldn't have in async mode. Sounds like more of a footgun you can hand yourself.
- jolux 5y agoThis is interesting, it seems like a global mode that kind of turns normal Zig code into Go or Erlang-like non-blocking code. It does mean you have to hold non-local context in your head while reading functions, but maybe that's a worthy tradeoff?
- messe 5y ago> This is interesting, it seems like a global mode that kind of turns normal Zig code into Go or Erlang-like non-blocking code Nope! There's no language magic going on, and the global switch is just a global variable in the root module. It only affects a few functions in the standard library that check that variable. Zig relies heavily on compile time code executing for metaprogramming, and your code can read symbols from the root source file via @import("root"). All that pub const io_mode = .evented; does is set a variable that the standard library then checks. When io_mode is set to .evented, the library sets up an event loop and makes async calls internally. When it isn't, it makes blocking calls as you would expect. Unless you explicitly use 'async' to call functions, then they're going to act as if they block as they will be immediately await-ed upon, so there's no change.
- jolux 5y ago> Nope! There's no language magic going on, and the global switch is just a global variable in the root module. It only affects a few functions in the standard library that check that variable. I didn’t say there was magic, I was describing the practical effect of this feature. On a type-theoretic level, it’s like entering a global async monad, which is roughly the structure that Go and Erlang have. But since you mentioned it, global variables that have side effects are definitely magic, in my view. If I did this in Ruby by monkey-patching synchronous IO methods in response to a global flag, people would definitely call it magic. And I understand how the feature works, but my reservations about it remain. As for results being immediately awaited meaning there’s no change, I think I disagree. What happens if another task fails and brings down the whole system while I’m awaiting something? It wouldn’t have been scheduled in if I was programming synchronously.
- deleted 5y ago[deleted]
- meowface 5y ago>But since you mentioned it, global variables that have side effects are definitely magic, in my view. If I did this in Ruby by monkey-patching synchronous IO methods in response to a global flag, people would definitely call it magic. It definitely is "magic", but it's one of the few areas where I welcome it, personally. What other options are available? The three I know of are function-colored (JavaScript, Python), async/automatic context-switching by default (Go, Erlang), or monkeypatching to go from colored to async/automatic context-switching by default (Zig, Python gevent). I also hate globals, side effects, and monkeypatches, but I find the monkeypatch method to be the best compromise. I still use gevent in pretty much every Python project, and have for the past 10 years.
- gpderetta 5y agoVery nice, but how does it support separate compilation? Does the compiler always compile two versions of each function or zig only support whole program compilation ( by JITing for example)? Or is it just stackful coroutines and there is really no difference between async and sync functions?
- aeldidi 5y agoI believe it's up to the programmer to implement this. Here's some code from the zig github[1] page that illustrates how it might look once zig is stable (this is from an issue). pub fn write(file: *File, bytes: []const u8) usize { // Note that this is an if with comptime-known condition. if (std.event.loop.instance) |event_loop| { // non blocking version } else { // blocking call } } So I guess functions that don't support async could just do something like: if (!std.event.loop.instance) @compileError("X function only supports async"); [1]: https://github.com/ziglang/zig/issues/1778 https://github.com/ziglang/zig/issues/1778
- gpderetta 5y agoThanks, but how is a waiter suspended? The typical await implementation will do a CPS conversion of the waiter function, but to trigger the conversion it needs to see an await call, or pessimistically generate two versions of each functions. But from your example it seems that Zig just do context switching (not knocking it, I'm a big fan of it over await), but then why would it need an await/async keyword at all?
- gpderetta 5y agoTo answer myself. Zig doesn't really do separate compilation: https://github.com/ziglang/zig/issues/233 https://github.com/ziglang/zig/issues/233