Zig 0.12.0 Release Notes
ziglang.org
ziglang.org
Some notable changes in zig.guide since Zig 0.11 came out:
- Changed from following master, to following major releases (Zig is more stable now, less work for me. Yay.).
- Migrated from Hugo to Docusaurus, bringing some large frontend improvements.
- Moved over from ziglearn.org to zig.guide.
- Removed 'usage in production is not recommended' from the front page.
- Support for versioning.
- Greatly improved testing through build.zig.
- Opened up for github sponsorships.
- Added another section for supplementary walkthroughs.
The site still isn't nearly where I want it to be but there's been some big improvements in the last few months. - Migrated from Hugo to Docusaurus, bringing some large frontend improvements.
Does this mean Docusaurus has more or better features? I do not know either of them very well but looking for a solution to publish md docs.It kinda boggles my mind in particular that they’ve left async unfinished all this time. I can argue myself into tolerating an unfinished stdlib until the 1.0 release, since at least the interface should be pretty stable, but it’s been years, and async is an essential differentiator in this domain that needed to be nailed down early so that an ecology and craft theory could start developing around it. Instead, not only does Zig still lack a production-quality stdlib today, but it’s basically a nonstarter against the main contenders without proper async.
Maybe the thing to do would have been to offer some type of sanitary minimal API for a stack object + state machine + set/longjmp builtin primitives that have well-defined semantics so that at least people could get started defining usable APIs on top of it and experimenting with it.
Instead there was a false start on it with a short stop.
I don’t understand the mindset that led to this (non-)decision, but it erodes my confidence in the project. I understand that it’s not meant to inspire confidence and that it’s still experimental, but at this timescale it is simply frustrating to see and wish for a better status.
I know, I’m not wearing their shoes or whatever. Maybe I’m being uncharitable, but I don’t think I could really be accused of being impatient at this point.
In fairness, I feel this is a major hole in nearly everything but Go, so I guess getting it right is important.
Async/await is a nice-to-have syntax sugar, but not required to write asynchronous code, and considering the substantial under-the-hood complexity I'm not sure if async/await is a good thing overall, especially in a low level systems programming language like Zig (or FWIW, also Rust and C++).
Arguably, promises are an important concept in JS because it's a convenient and composable method to let code complete "in the background" without proper multithreading, but promises work just fine without the async/await code transformation magic (which even in JS is just the cherry on the top, but not a must-have feature).
> sanitary minimal API for a stack object + state machine + set/longjmp builtin primitives
Traditional async/await doesn't use most of that but instead relies on the compiler to transform sequential 'async code' into a switch-case state machine. What you want sounds more like green threads / fibers with stack switching. This isn't portable to some platforms where the call stack isn't accessible (like WASM). But as I wrote above, I think that lowest layer should simply be 'stdlib functions with completion callbacks'. Those low level functions can then be wrapped into all sorts of asynchronous/multithreading runtimes.
FWIW, I think a stack-switching fiber runtime should be possible in Zig without special language primitives, because Zig has inline assembly: https://ziglang.org/documentation/master/#Assembly
I can see arguing to omit it entirely from a language, and to never introduce it, but clearly the Zig authors intend to include it.
Zig's initial async/await solution looked like it would avoid a lot of that mess, but IMHO it's better to not have a feature at all instead of a half-assed feature. So I actually appreciate Zig to focus on other things first if they don't currently have a good plan on how to provide a good async/await implementation that fits the "Zig vision". Personally, it's definitely not anywhere close to the top of my "must have" list for a low level language like Zig.
For example, an http server is using nonblocking calls and dispatches to its own threadpool. The lack of cohesiveness means that the blocking call made by the PostgreSQL library can't feedback into the http server's concurrency model. The http server could expose or pass some control mechanism into the application, but for that to work, all the libraries would need to support it.
A lot of people don't seem to think this is necessary, and I've generally assumed that they're right and there's stuff I just don't understand.
IMO, we haven't really seen a natively compiled and manually memory-managed language "get it right" yet. There are pretty good third-party abstractions for it though.
Which domain are we talking about here?
> a major hole in nearly everything but Go
... because Go being mostly used as "the devops tool language" where I honestly don't see much if any point to async code.
No sorry for you guys, you made some freaking good stuff, people wanna use it in production already, deal with your success. And there aren't even many pain points with the language itself. Most of them are meta. Here are my pain points:
- Documentation: Step it up... this is a mess. I might be able to learn it because I learned many languages over the years, but many people will just give up frustrated and never turn back. And even so I am mostly OK with the docs, I still miss stuff.
One of the worst things could as well be its own point: Document the build system darn it. It is so hard to learn, that I prefer using any other build system or even creating manual zig commands.
- ZLS needs to be merged as part of the project. The fact that it is not always on par with every commit is annoying. A language server should be an inseparable part of a language nowadays.
- And now to one of the few pain points I have with the language itself: You cannot be serious about that "underscore, unused variable" solution. That is so extremely ugly and code polluting.
I know this critique was harsh, but it is because I love this language and I think it will take off so heavily :). My fear is that if those things are not considered that at a certain point users will be annoyed by Zig and it will lose a big amount of userbase.
Considering that a major goal of Zig is to build and maintain robust software, the decision of making unused variables an error is interesting and worth exploring. It works pretty well in practice when we use ZLS, which goes back to your point about making ZLS part of the standard toolchain.
> deal with your success
And how exactly should the Zig team deal with its success? That's a small team, with limited funding. Do you think they should prioritize the pain points you listed over the ones in their current roadmap?
>This Release Contains Bugs, Zig has known bugs and even some miscompilations.
>Zig is immature. Even with Zig 0.12.0, working on a non-trivial project using Zig will likely require participating in the development process.
I dont think documentation should be priority for Zig at this stage, just read the Release Note and you will see how things are changing and breaking all the time. But I do agree certain things should be documented, as suggested in another reply is the Spec and build system.
> My fear is that if those things are not considered that at a certain point users will be annoyed by Zig and it will lose a big amount of userbase.
They won't use much user base as that kind of users will remain committed to using corporate managed languages.
It’s very much like low level go and feels like home for people with Typescript experience.
Even with all the little quirks and gaps, it’s my new fav language.
The other day I ported a c implementation of bsdiff to zig and was able to make it go 20% faster and compress patches 10% smaller.
Very excited for the future of zig. The stated mission of replacing c and mindset of the people building it align well with how I think. Can’t wait to update to 0.12.0 when I’m back at my laptop.
> Oven has raised $7m in funding led by Bucky at Kleiner Perkins with participation from Guillermo Rauch, YCombinator’s Continuity Fund and more.
> Oven will provide incredibly fast serverless hosting & continuous integration for backend & frontend JavaScript apps — and it will be powered by Bun.
> The plan is to run our own servers on the edge in datacenters around the world. Oven will leverage end-to-end integration of the entire JavaScript stack (down to the hardware) to make new things possible.
Great work on Bun. Excited to see the future of your project.
Reading other people's Zig code is usually a joy, as is writing it
const connection = try http_server.accept();
defer connection.stream.close();
This pattern of reaching into the returned struct's internals is so weird to me. Why not just connection.close()? Unnecessary Use of var
Sounds like yet another case of "this should be a warning instead".Agreed. There should at least be a way to turn this into a warning during development.
But TBH, I'm using similar strict error settings in TS/ESLint for a long time now (as part of our company linter rules) without autoformatting and it's fine.
So this is an IDE-mandatory language which does not come with an IDE?
> autoformatting through the Zig language server by inserting or removing '_ = unused_var;' as needed.
So the error has become a non issue by completely undermining it? That seems like the opposite of a good idea.
> I'm using similar strict error settings in TS/ESLint
A linter or an IDE "error" is equivalent to a compiler warning: they don't prevent compiling and running the code.
And JS/TS's const is much weaker than Zig's, essentially trivial: zig's const means the value is immutable, not just the binding.
Nope. AFAIK the autofixing is actually implemented by `zig fmt`. The IDE integration just makes that feature available as "VSCode Formatter".
> So the error has become a non issue by completely undermining it? That seems like the opposite of a good idea.
Nope. The important part is that the autofix works in both directions. E.g. if an unused-fix was added before, it will be removed as soon as the variable is used. This makes the development workflow painless, but at the same time the code cannot become littered with redundant unused-fixes. It's also trivial to search for the "_ =" discard pattern to find any leftover discards (in a 'final' codebase there shouldn't be any of those).
> A linter or an IDE "error" is equivalent to a compiler warning: they don't prevent compiling and running the code.
Nope, in our case it's an actual hard compile error. You can't "npm build" or even git-commit a project that has unused variables (which are not explicitly discarded).
Except yes, and your clarifications confirm it. If the formatter automatically adds and removes artificial uses for unused variables then you're undermining the diagnostic since you're automatically suppressing it. It's completely useless. Worse than useless even.
Not to mention zig's diagnostic is as crap as go's: it doesn't track unused stores, so it only catches a minuscule number of possible issues, and misses the much larger one, yielding both incredible annoyance and false confidence, all because the maintainers of both languages refuse to support warnings.
> It's also trivial to search for the "_ =" discard pattern to find any leftover discards (in a 'final' codebase there shouldn't be any of those).
Meanwhile a warning actually tells you directly and reminds you, without having to "search for the discard pattern".
> Nope, in our case it's an actual hard compile error.
Boy am I happy not to work with you.
It seems like you're forgetting that it shows up in diffs (code reviews) and text editors.
So it’s worse than useless, it’s actively harmful.
It wasn't my decision to introduce such draconic rules, but in practice it turned out to be a non-issue. Production code should be warning-free, and warnings should be treated as errors. Two simple rules that both make a lot of sense, no matter what language.
PS: that 'unused variables are errors' linter rule is actually coming from the airbnb eslint plugin (which AFAIK is quite popular in the JS/TS world: https://github.com/airbnb/javascript/blob/c25bce83be4db06e6a...)
this doesn't help anyone writing autogen code, though.
The discussion about it is indeed interesting: https://github.com/ziglang/zig/issues/224
e.g.:
> > When debugging/prototyping, it's useful to comment out a line without having to refactor, e.g. > > I think this use case is a strong argument. However, it's up against another strong use case, which is the ability to rely on a set of assumptions when reading the source of a compiling codebase. > [...] > a variable declared with var does have a modification to it
Which is an interesting property.
We'll see how it goes for them.
Personally, I feel that if you want to grow as a programmer, no matter what you're doing, there's little that offers more bang for your buck than learning C. You can pick up C relatively quickly, and that knowledge will serve you (maybe mostly indirectly) throughout your entire career.
Learning Zig is a much better option. Too many quality of life improvements over C, and you still learn the same fundamentals. If you're interested, I wrote something that might help (1).
As for _using_ Zig. I think it's a good idea to take a conservative view and assume the ecosystem is hostile. The quality of test coverage of the standard library varies. Breaking changes are frequent and aren't always trivial. 3rd party libraries are often abandoned and many are written by people learning Zig (including my own!). It's really great for learning/side/fun project because you can get distracted for weeks writing something you never intended to.
It's not actively hostile, but I think approaching it that way will help manage expectations.
I would strongly consider tracking the master branch. If you write anything substantial in Zig, you will 100% run into a must-have bug-fix or feature in the stdlib or 3rd party library that depends on on 0.13.x development commit.
With that in mind, how does Zig compare to C, complexity-wise? Both for learning, and its implementation.
Is it a 'bigger' language than C? Just different? Easier to grasp? Or takes more effort to get a good understanding of? About the same as C?
Anyway: a very promising project. Nice to see it progressing towards a mature ecosystem.
1. A "better C" in the sense that the language core of Zig fixes the commonly known and accepted design warts of C which are hard to fix because of backward compatibility requirements - this part is actually less complex and easier to learn than C because there's less implicit and less unexpected hidden behaviour while having roughly the same "language surface". If you are used to C's somewhat sloppy "hippie-approach" to programming, be prepared that the Zig compiler will yell at you a lot at the start of the learning phase, especially when it comes to implicit type conversions which follow very strict rules in Zig (when compared to C).
2. A much richer and actually useful stdlib (in this area Zig is also a much better C++), don't quite expect a Python-style "batteries included" stdlib though.
3. A much more complete compiler toolchain package, with a build system, package manager, C/C++/ObjC integration and cross-compilation to the important target platforms included and working out of the box (and most of that integrated into the Zig executable, a vanilla Zig install actually only has a single executable, the Zig compiler).
4. And finally the "visionary part", where Zig differs most from C and other languages and explores its own path, like the approach to comptime, generics, type reflection and error handling.
An `if` statement in Zig has a few different faces, traditional condition, unwrapping an optional and unwrapping an errorset.
That makes the language "bigger", but these are things you'll regularly face in C anyways, even with trivial examples. I think most of the things Zig (purely as a language) has "added" to be labeled a "Better C" are all pragmatic and don't increase any cognitive load.
The other thing worth mentioning is that Zig has a number of safety advantages. I just touched up my DuckDB driver, and their C API exposes the underlying storage for a column as a (void ), the format of which depends on the column type. In Zig, I can map this to a typed tagged union. For an bigint column, instead of a (void ), I end up with an []i64.
if you've never heard of them:
https://c-faq.com/decl/spiral.anderson.html
I'm not even sure if the faq is in jest or not.
The thing that isn't well known about C type syntax is that there is no type syntax. At least originally, there wasn't really type syntax. (This was later muddied by e.g. types in function signature parameters, which came from C++ -- and which are a good thing to be clear).
Types follow usage (given as normal code expressions), and if you can read normal expressions, you'll know what a type declaration means.
The syntax is simply: type-name EXPR. where type-name is a simple name (or tagged name, such as struct mystruct), and EXPR is a limited subset of normal expressions. Example: int *foo[10]; Read like (type-name = int), (EXPR = *foo[10]). Now, if you now how to understand the EXPR, you'll know what the type of the variable foo is. Since (*foo[10]) must be int, you can simply go backward to know the "shape" of foo.
Similarly, there is some elegance how to define a type name instead of a variable -- you simply add the "typedef" keyword and that's it.
Even though that idea is a bit arcance admittedly, and not so pure anymore today, now I'm used to the C way of declaring types, and I think I like the fact that there isn't much of an additional syntax. Maybe it even removes mental overhead when reading code.
mytype *foo;
Could be a variable declaration,
Or is it?Suppose you had
int mytype = 42;
int foo = 1;
Before it. The C compiler will happily turn the statement into a multiplication with the result going to /dev/null var foo: mytype;
Can never be parsed with any other meaning depending on how the chips fall after parsing (and possibly analysis, given that macros are a thing) the entirety of the file.For writing a batch parser/compiler that properly reads all the includes too, the context sensitivity is only a theoretical problem. Adding a type table is easy.
Not having to type "var" all the time is very nice on the other hand.
Pity that even Dennis Ritchie failed to convince WG14 about fat pointers.