Zig 0.16.0 Release Notes
106 points by vpol
106 points by vpol
The 'Juicy Main' bit is interesting. I don't think I've seen this approach taken to this level. I know that in C/C++ you can optionally have argc and argv (apparently envp is implementation defined) and in Austral, you can optionally have a RootCapability. Are there other examples of this (besides languages doing the C/C++ thing)?
Ooh, juicy main looks great. That'll cut down on a lot of the boilerplate when starting a new program. I was literally thinking earlier "I'd like to start a new zig program, ugh gotta start by cutting and pasting some boilerplate from my other programs".
Congratulations on the release!
Super excited to kick the tires on std.Io. Even ignoring sexier justifications around function coloring and swappable concurrency models, injecting IO just feels like the right way to do things. Isolating effects isn't just for the functional peeps ;) . It enables so many ergonomic debugging and testing capabilities. Not having to awkwardly stub out IO-using dependencies for tests is going to be so nice for larger projects that need to ensure correctness.
"Juicy main" is a huge quality of life improvement. Avoiding the allocator setup preamble for quick programs is great. It was just a few lines, but was just enough friction to be annoying. I expect more than a few past-newcomers bounced off there before really trying the language.
Was surprised to see the "Forbid Trivial Local Address Returned from Functions" section. I didn't realize that was in the works. It hides a link to planned work to error on returned local references too! I don't think I've run into these issues in years, but I definitely did when starting out with manual memory management. That should go a long way for anyone hesitant about Zig's safety story.
injecting IO just feels like the right way to do things
Agree, when I was a junior I kept bugging seniors about how to test code that does I/O. Their consistent response was basically "wrap the world in your own interface and pass it in". Then it is mockable, testable, etc. That always seemed so wrong to me. Turns out all languages before Zig just did it wrong and having your stdlib designed to pass I/O in is indeed the right way.
Edit: of course even without the language stdlib supporting you like Zig, software design concepts like "functional core / imperative shell" would have been helpful from the seniors.
The integer and float casting improvements are all very neat:
const f: f64 = @sqrt(@floatFromInt(N))const a: u8 = @round(f)Things like this were always a pain in the neck for me, since my main Zig project is a little roguelike. I'm sure it's not what folks think of when you say "game development" (I'd call it "lite game development" instead), but it helps all the same! Can't wait to see what other ergonomic improvements make it out.
I'm also happy to see the Future type and general async being added to the language again, even if it's only in the standard library. In the past I (ab)used async to produce generators, which I used heavily for things like animations and such. Those animations became 1.5-2x code when I had to convert them to a state machine, so I look forward to seeing if the new async system is capable of the same thing.
environment variables are available only in the application's main function.
I recall that glibc has a nonstandard extension where, if a certain symbol is defined, glibc calls the symbol with a copy of the environment map and thus allows dynamic and shared libraries to access the environment without being passed a copy. Would this be considered as a Linux-only alternative?
when I had to convert them to a state machine
What was the migration like? Were you able to only transform the implementation and maintain the overall API or?
Honestly don't recall (though yes, the API was changed, though only somewhat since the steps are the same: call a function to get the initial animation object, call next() or continue on it). I know one of the animations was a PITA, but that's probably just because I was smooth brained and couldn't keep track of where the animation was doing what and how to cleanly turn it into a state machine. Before, After. (Yes, all terrible code, of which I'm deeply ashamed )))
Zig is undergoing a lot of churn. I am not really seeing a rhyme or reason for most of it.
I used to use it, but I got sick of re-writing my programs - so at this point I think I'll wait until Zig 1.0, which is realistically some time next decade.
I've been following along since Zig 0.11 or so, and it does sometimes feel like churn but most of the improvements are for the better, and you gain new features along the way. I ported a large codebase to Zig 0.16 and it's quite approachable.
I'm really liking where things are going.
You can always leave old programs using old versions of Zig. Things like the Zig Version Manager make it easy to use multiple versions. I do suggest using more recent stuff to get all the latest goodies they've been cooking up.
I think the zig "philosophy" is in many ways alien to me. I loved the arbitrary sized integers, the declarative ways to pack your structs, the BYO allocators, the easy ability to just use C code directly... comptime was also pretty nice.
But it's just way too opinionated and quirky. I think if I list everything, people will find it mean... but they are just off on their own planet sometimes.
I think if I list everything...
Could you list your biggest thing? What's the thing that makes you think they're "off on their own planet"?
It's all just a bit much for me.
I do agree the test discovery could improve. I don't know if the core devs have any plans to, but I've had tests I thought were running that weren't discoverable.
as someone who loves Zig, this is currently my biggest gripe. my maybe controversial hot take is, when building the test executable, all test blocks should be required to be reachable from root with no exception. you lose tests in comptime conditional modules, but gain test determinism. the comptime (generics, conditional inclusion) should be in the test blocks, not gating them
having to write your build script in an imperative systems language (ie, Zig) rather than just having a config file
Is big win for me, zig's build system is quite powerful. I do agree that tests are wonky.
I'm not quite sure who are "they" and how do you know that they don't know OCaml. Seems to me that there's quite a high probability that a programming langugages nerd would at least be aware of OCaml.
The cancellation part looks extremely interesting this is something that rust definitely struggles with. I don’t completely understand the section here. Do they drive the future to completion and disregard the result if you cancel? How do they prevent things like futurelock or just general issues caused by a canceled future effecting other parts of the code in unexpected ways
These questions are difficult for me to answer because despite implementing async/await in Zig 4 different times in 4 different ways over the last 10 years, every time I've tried to understand the way async works in Rust I have failed to develop a coherent mental model for it.
To the best of my understanding, there's no such thing as "futurelock" in Zig, and none of the other footguns with Rust async, because it works fundamentally differently. When you async something in Zig, it starts happening right away. It doesn't get "polled" later by an "executor" or whathaveyou.
From the notes:
When cancelation is requested, the request may or may not be acknowledged. Acknowledged cancelation requests cause I/O operations to return error.Canceled. Even Io.Threaded supports cancelation by sending a signal to a thread, causing blocking syscalls to return EINTR, and responding to that error code by checking for a cancelation request before retrying the syscall.
In general, cancelation is equivalent to awaiting, aside from the request to cancel. This means you can still receive the return value from the task - which may in fact have completed successfully despite the request. In this case, the side effects, such as resource allocation, should be accounted for. Here is an example of opening a file and then immediately canceling the task. Note that we must account for the possibility that the file succeeds in being opened.
Gotcha, that framing actually makes it make more sense for me. Are stackless co-routines planned as another option?
I am probably not qualified to answer this, and responding in hope that if I am understanding it incorrectly someone will correct me so I can learn something.
most (all?) IO calls can return error.Canceled. when a task is cancelled, the task chugs along until the next call to such a method. the task can choose to do whatever it wants with that information: cleanup, ignore it, stop immediately. once the error is handed to the task, however, the canceled flag on that task is reset. if it wants to, it can recancel itself to propagate cancellation onward.
there's also "cancel protection", which is I think critical in preventing futurelock by giving the task the ability to clean up properly.
so cancellations are requested, can be respected or ignored, and the future locking is prevented because they get to cleanup while protecting from more cancellation.
I had a conversation with Andrew at some point about how syscalls are interrupted, but it was slightly over my head.
I had a conversation with Andrew at some point about how syscalls are interrupted, but it was slightly over my head.
I'm sorry for making it more complicated than necessary! It's quite simple, really. At a low level, the cancel request makes a tgkill(tid, SIGIO) syscall, where tid is the thread id of the task being canceled. Even though the signal handler is a no-op, it causes e.g. read() or write() to return EINTR, giving the Io implementation a chance to check if cancelation has been requested.
Oh wow the cancel protection is very neat I'll have to read into that a bit further thanks!
The art next to the post is really nice.
https://ziglang.org/img/Carmen_4.svg