Zig: Upcoming release postponed two more weeks and lacks async functions
ziglang.org
ziglang.org
Cool, I like this!
I realized last year that I was investing too much time into sharing things on Twitter that I'd subsequently forget about or be unable to find.
Instead, I created a separate section on my personal blog for "notes" where the idea is to house content that I'd otherwise post to Twitter. It's been working well, and I like owning my own content rather than contributing it to another platform.
I'm especially glad for this change after seeing what's happened in the past few months with Twitter and Reddit. It's been unfortunate to see those platforms become so much more possessive of the content that their users generated. If you publish to your own platform, you're safe from that. At least until LLMs bury your platform in noise.
Curious, but how exactly would they do that? Are you talking about scraping your website and adding garbage filler content from LLMs and republishing to divert traffic?
I don’t understand why Google is doing so poorly at filtering it out: for a concrete example, searching for terms relating to common semi-trivial problems when programming mainstream platforms like the web, Java, or C# will now result in low-quality, non-authoritative sites like GeeksForGeeks - or even W3Schools always coming up first, with authoritative sources like MDN, the W3/WHATWG, even StackOverflow sometimes being below-the-fold.
You can even create your own custom Google search that only returns results for domains that you list. So you could put stack overflow, reddit, and a bunch of reference sites (like mdn) and have super high quality search results.
To make it as convenient as possible (every amount of friction would eat at my motivation) I made it using Obsidian Digital Garden [1]. It's a bit of a hassle to set up and the UI is annoyingly bad but it's pretty convenient (boils down to click to publish) afterwards.
"A delayed game is eventually good, but a rushed game is forever bad." - Shigeru Miyamoto
The same goes for features. I'd rather Zig have a delayed/good async next year and forever after, instead of a rushed/bad async right this moment and forever after.
Seems like Rust is still working on this area with the controversial keyword generics
I don't think closures have anything to do with it.
There was also some debate about how cancelation of an awaitable function built with those primitives would work. I don't think there was a great answer for that yet.
In C#, they implement Await/Async by converting the function into a Class, just like when you use 'yield return'. Control flow is all over the place.
Zig is so strict about "no hidden control flow" that you can't even have destructors (code which runs when a variable goes out of scope)
Additionally, zig provides this functionality with defer and errdefer, giving you the ability to explicitly call a function at the end of scope.
Those tend to be GC languages though, where deterministic execution is not a hard requirement.
There isn’t implicitly called destructors because the languages mantra is “no hidden control flow”. RAII et semantics is almost entirely hidden control flow that you just have to “know”.
Lots of people hate when they need to “just know” things to fully parse the code they’re reading.
You’re free to dislike those decisions, of course. Personally, I like the target of no hidden flow.
The big problem with defer/errdefer is that I have no way to mark a function as "This function always needs a defer after it and the compiler needs to yell at me if it doesn't exist."
It's also sometimes really hard to scope your defer/errdefer correctly. You may have to twist your code inside out because defer/errdefer ends at a block scope while your variable may not (via: "break :blk varname;").
This all bites so hard for things like reference counting. The bugs ... the bugs ... <mumbles huddled up in corner>
You can do as you say, but that winds up being a lot of non-enforced boilerplate. And you can get subtle errors if you miss the defer at the wrong scope (ask me how I know this ... actually, better yet, please don't as it will give me flashbacks :) ). And neither the language nor compiler can help you. This contrasts with, say, Rust where reference counts or locks can be enforced by the compiler and simply never go wrong.
defer/errdefer is obvously vastly better than C. My codebase was painful in C. Zig made it tractable, but it was hardly pleasant.
Unfortunately, I really don't have a good suggestion as to what Zig should do instead. defer/errdefer is a minimum, but it's not clear what a single, better step further actually would be. Most solutions in other languages wind up with RAII and that invokes a nightmare of cascading design decisions through a programming language that Zig very much does not want to follow.
It will be interesting to see where async/await finally lands. I think that will have some component of a "slightly better" solution.
Zig just needs some runtime event loop like Tokio or AsyncIO from Rust to get up and running with its fantastic async model.
What alternative approach would you have preferred Python do? While I’m not a fan of the coloured-function approach either, I can’t think of a better alternative that checks-off the most boxes (Java’s Loom looks interesting but in-a-way it seems too-good-to-be-true to me, while low-cost approaches like Zig’s would introduce too much complexity for novice users and people wanting something that just-works)
[1]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
A function which contains a suspend point becomes implicitly async. An await is implicitly a suspend until the Frame completes, so it also makes the caller's function async. Calling a async function without the `async` keyword is implicitly `await (async f(args))` so same deal.
You ended up not needing to know if `f()` is async or not when you call it ("effectively colorless"). If it is, it just makes you async and bubbles up until the sync boundary (an `async f()` call, `main()`, or an `export fn` C API).
Zig's stdlib could handle the main() case and spin up an event loop if you requested via `io_mode = .evented`. Some stdlib stuff like net/fs would then use the event loop while others like os/Thread would stay the same if you still wanted to do sync stuff.
This was the common case, but async is a lang construct not an stdlib construct so it works for all targets. For example, you could use it in wasm, the kernel, etc. You would just need to write your own starting / scheduling of the frames, known generically as an "event loop".
"Async functions can be called the same as normal functions." Only when you have an event loop, the order async functions are running might be different because of the scheduling, but everything else is the same.
Andrew Kelley is the greatest, most productive Yak shaver of all time.
When it comes to e.g. memory management, Zig tries to be unopinionated and allow different implementations to be implemented as desired; so it seems odd to bake-in something like async/await (even if the execution strategy of those computations is up to the user).
I've seen this happen in many high-level languages (JS, Python, PHP, etc.), which I mostly attribute to (a) ignorance of those generalisations, and (b) a band-wagon effect. The unfortunate result in those languages is a bloated mess of try/catch, async/await, for/yield, apply/return, etc. and all of their O(n!) possible interactions; which could have instead been implemented as libraries on top of a single primitive (e.g. shift/reset, or whatever)
[0]: AFAIK these are all equivalently expressive, and given one it's easy enough to write the others as libraries.
PS: I recall asking this question when PHP added generators; I can't seem to find a bug report or mailing list post though...
The key requirement for all these is an ability/API to defer and resume execution (indeed, delimited continuations are sometimes described as "resumable exceptions"). In higher-level languages we'd just assume the presence of first-class functions/closures, and use those to describe these features. I'm less familiar with how that looks in a very low-level language like Zig, however these "async frames" appear (to my naïve eyes) to be analagous; hence why I'm interested whether one of those more-general primitives could be provided instead (the answer might be no!).
As for clarification on those features, here's a quick attempt. Firstly, note that all of these approaches are basically APIs to construct, consume, and combine (deferred) computations: they are unopinionated on how those computations get run (e.g. the user could supply a "main loop", or whatever).
Delimited continuations are like exceptions, except the stack (AKA continuation) is passed to the handler, which may choose to resume it. We can implement coroutines/async/yield/etc. by having handlers which put their continuation in a queue and pop off some other one to resume instead; we can get data parallelism by resuming a continuation many times; we can get backtracking by remembering old continuations and trying them again; we can get parsers, probabilistic programming, nondeterminism, etc. https://en.wikipedia.org/wiki/Delimited_continuation
Algebraic effects are similar, but defunctionalised: i.e. they represent control flow with datatypes, and are "interpreted" by a user-specified function.
Applicative is an API for combining two computations concurrently, e.g. the product of Foo and Bar is a single computation yielding a pair of results. Monad can likewise combine sequentially, where Bar depends on the result of Foo. These feel more abstract, but are equivalent to the above e.g. see https://okmij.org/ftp/continuations/implementations.html https://reasonablypolymorphic.com/#understanding-freer-monad...
- 'Yield: Mainstream Delimited Continuations' builds them up by generalising for/yield (rather than try/throw), but the result is the same: https://www.researchgate.net/publication/228584945_Yield_Mai...
- 'A Poor Mans Concurrency Monad' seems to be the origin of async/await pattern (according to https://softwareengineering.stackexchange.com/a/377514/11211... ), which is defined as a monad (technically a monad transformer) https://www.cambridge.org/core/journals/journal-of-functiona...
Incidentally, monad transformers are an attempt to work-around a deficiency of monads: that they don't compose. Algebraic effects have become popular precisely because they do compose.
[0] https://docs.godotengine.org/en/stable/tutorials/scripting/g...