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