Hi danvk, thank you for sharing your writing, I think your comments about having TS introduce runtime checks for things in debug mode is a truly good argument to make.
I’ll post some comments about things that I noticed while reading, most are nitpicks / small comments, I only have a more structured comment about the dev tooling section of the blog post.
– begin
Zig also embraces best practices that have emerged over the past few decades: immutability as a default,
Zig really only defaults to immutability when it comes to function arguments, most of the rest is mutable by default, most notably pointers: *Foo
is mutable, *const Foo
is immutable.
I’ll talk about why function arguments are immutable when commenting on a later part of the post.
Now the safety checks are off and the integer overflow is allowed to happen.
To be precise, integer overflow is not expected to happen. In Zig integer overflow is considered UB so, in unsafe release modes, the optimizer is allowed to leverage the assumption that it won’t happen, resulting in arbitrarily bad outcomes if that expectation is violated (although in practice it usually just means that the integer wraps around, which can also be super bad in reality, but in a somewhat less chaotic way than “fully” unpredictable UB).
To get integer wrapping (as a guaranteed, expected behavior, not an accidental one), you must use dedicated operators like +%
and -%
(there’s also saturating equivalents of these operators, like +|
).
I will come back to this point again in a later comment.
Inserting runtime checks would allow TypeScript to flip over to an “innocent unless proven guilty” model like Zig’s, which would result in fewer false positives and make noUncheckedIndexedAccess
easier to adopt.
This is so good, great point.
- Language service
I’m used to seeing these things being called Language Servers, are those usually called services in TS lingo?
He also mentions that he uses vim and does not use a language server, so maintaining one would be a tax on his time with no benefit.
Regardless of Andrew’s personal preferences when writing code (which I believe are actually not well represented here, more on this later), there is a plan to get good code inteligence from the Zig compiler and the main blocker is just that it’s a bit too early to start working on it yet.
Check out this blog post I’ve written recently on how to get much better diagnostics from ZLS, in there I also mention the long term roadmap plan for a complete language server solution.
My point in general is just that, while it might not look like so from the outside, there are still a ton of foundational pieces missing in the Zig compiler toolchain that must be implemented before we can get to DX tools. As an early adopter one feels some pain points and that makes one think that solving those should be prioritized, but that very rarely aligns in a meaningful way with the development roadmap.
As a small-scale example I’m working on a static site generator with its own custom templating language and I spent the last few weeks writing a HTML parser. I can totally imagine a super-early adopter getting burned by my error messages (eg @panic("TODO: explain that super must have a template attr")
), but at the same time I know that makes no sense to prioritize fixing those templating-language-specific TODOs over first making sure that I can provide excellent diagnostics for basic HTML (which btw led me to publishing a HTML LSP).
Going back to Andrew’s stance on dev tooling, he actually has mentioned multiple times that he wishes for IDE-level support for stuff like “refactor into a function”, “rename symbol”, etc.
The problem is that at the moment we don’t have that yet and the Zig compiler has some pretty taxing files (eg Sema.zig, almost 40k lines of code) that are hard to handle for ZLS (or at least were at some point in time, I doubt he tried recently)
It wasn’t obvious to me why some failure modes (out of memory) are handled with explicit errors, while others (integer overflow) are handled via detectable illegal behavior.
This gets us back to the problem of “allowed to happen” vs “not expected to happen”.
You don’t get an error because the idea is that by declaring overflow as UB, the compiler can potentially elide all kinds of checks, which is mutually exclusive with the idea of handling overflow with an error. More in genral a Zig error value is for expected conditions (that are usually part of the unhappy path), while panics are for unexpected conditions, also known as programming errors. Triggering an integer overflow with a non-wrapping operator is a programming error.
If you do want to have math operations that are expected to potentially fail, std.math
has all of them as functions that return errors for overflow, division by zero, etc.
Zig implicitly copies data all the time. Sometimes this can be subtle. Function parameters are passed by value.
The previous point (right before the sentences I quoted here) about Zig copying values is correct, but when it comes to function parameters things are more nuanced. When you pass an argument to a function by value, the Zig compiler is allowed to implicitly pass it as a pointer instead. Roughly speaking, small structs will be copied, big structs will be passed by reference. This is why function arguments are immutable: mutating them when copied would not be a problem, but mutating a referenced copy would be, and so you can’t modify them no matter what.
Just to reiterate for clarity: it’s an internal optimization so you treat the value as if it were a copy and, if it’s instead passed by value in the machine code, the compiler will make all the necessary adjustments to maintain correct semantics.
– end
I think you wrote a compelling article regardless of any comment I made here, but if you spend a little energy to work out the nuance around panics vs errors, etc, you will help people get a more correct mental model of how Zig works.
In case you might find them useful, here are a couple more links for you:
https://zig.news/kristoff/what-s-undefined-in-zig-9h
https://www.youtube.com/watch?v=TOIYyTacInM