Zig's Incremental Compilation Internals
mlugg.co.uk136 points by garyhtou 5 hours ago
136 points by garyhtou 5 hours ago
Zig's toolchain work is continually impressive. While I still don't plan to write software in it, given that I believe memory safety is table stakes, all of this stuff is very, very good. Before the incremental work, it was the toolchain and cross-compiler work. The toolchain stuff has continually been fantastic. I'm very curious to see what they come up with next!
> Semantic analysis is the most difficult part of the compiler to handle incrementally. Perhaps unsurprisingly then, this is where language design starts to matter a lot: while I am pretty confident that most modern languages could support incremental compilation similar to how we do, certain design decisions can make that much more difficult. Zig has had its design tweaked over the years (sometimes controversially) specifically so that it is easier to support fast incremental compilation.
This is something I wish that we had done with Rust. It is impossible to do all of the things at once, though, and we already had a tremendous amount of things to do. This is also part of the "when do you ship 1.0" tradeoff; for our goals with the language, 2015 was the right moment to launch, but if had a few more years to bake things, maybe we could have made compile times way faster. Software engineering is hard.
> I believe memory safety is table stakes
I'm not sure what that means. Java lets you do many things programs may want to do in a memory-safe way but not everything. Rust lets you do fewer things than Java in a memory-safe way, but more things than Zig. Zig lets you do fewer things in a memory-safe way than Rust, but more than C. So among these four languages we already have four levels of memory safety, none of them is 100%, all of them give up something in exchange for what they offer, and different programmers have different preferences for the compromise they prefer, and even that preference is context-dependent. Which of those less-than-100% memory safety compromises is the table stakes? And given that all of these compromises require something that could be quite substantial, depending on the circumstance, in exchange, and consequently programmers with the highest level of knowledge and expertise choose every one of those four in different situations, to me it seems pretty obvious that none of these is "table stakes".
I'll say the same thing I said to you as I said to Andrew, last time he and I talked about this: the way that everyone talks about memory safety (with maybe two exceptions, one okay (go) and one I dislike (fil-c)) is that "memory safe language" is about there being a clear delineation between what is memory safe and what is not, and that the unsafe aspect is a superset. Rust and Java both are memory safe, except where explicitly demarcated as not (unsafe in Rust, JNI or sun.misc.unsafe or whatever in Java). Zig and C have no such separation. When I (and others) talk about wanting memory safety, this is the important aspect of the design. This is what enables the "I know statically that a large part of the code is safe, and I also know where to check if something goes wrong" aspect of things.
Would you mind sharing some thoughts about fil-c? AFAICT its claims mostly check out so besides implementation details (GC?) it seems directionally good.
I'll come back and say some more in a bit, but in short: I like the idea of fil-c, but I also have some issues. I think it's overall a good and interesting project, but with some caveats.
Not OP, but AFAIK one big issue with FIL-C it does the checks at runtime, adding overhead, don't quote me on this, but IIRC is around 20% slower.
Okay so: in general, as a rule of thumb: anything that makes stuff have more memory safety is good. And experiments towards that end are also good.
What I do not like, primarily comes down to how the project is talked about and marketed. First, because it promotes an "us vs them" mindset, instead of a "we're all trying to improve memory safety" mindset, and second, because in doing so, it also overstates its case.
These things are sort of intertwined. Let's talk about the overstatement first. Fil-c has its own definition of memory safety that is slightly different than others. For example, I saw this recently:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct User {
char name[8];
int is_root;
};
int main(int argc, char*argv[]) {
struct User* user = malloc(sizeof(struct User));
strcpy(user->name, argv[1]);
if (user->is_root) {
printf("I am root!\n");
} else {
printf("I am not root :(\n");
}
return 0;
}
This, when invoked with "012345678" passed in, will print "I am root!". In my understanding, this is deliberately allowed.But beyond corners like this, fil-c's author will go on about "Rust has unsafe as a hatch, fil-c does not" while if you control-f for "zunsafe_" on https://fil-c.org/stdfil you get ... escape hatches.
The author regularly erases the difference between "traps at runtime" and "is prevented at compile time", which are legitimate tradeoffs where one or the other may be better depending on what you're doing. But they're presented as either equivalent, or one is superior, and I find this muddles the discourse. The performance issues also tie into this, "add a GC" is absolutely a valid way to handle these sorts of issues, but it is not the same thing as what Rust does. And that's okay! But presenting it as purely superior means that it's just hard to talk about.
Speaking of muddling the discourse, the author regularly trolls on X, providing tons of bad faith arguments and generally trying to rile up a "fil-c vs Rust" war that I think reduces our ability to talk about these differences in a calm, engineering focused context.
Finally, due to its design, fil-c is effectively Linux only. That's great for Linux, but many people also use other systems, and so it is not a meaningful option for them.
Anyway, after saying all that: I still think that it is a good project, and that it should exist and continue to be worked on. I just wish that the heat was turned down, and people could talk about the various approaches and their tradeoffs without turning it into a culture war.
I generally agree with all of this, but I'll add a few additional remarks. Because it's come up a bunch lately, I decided to do a bunch of code review/audit of the Fil-C codebase, and I'd say while it's got a lot of good bones, there's a long way to go to being a foundation I'd be ready to build on. I've reported a few UAF's upstream, and I've got a few PRs I'll add on, but if it only took me a day or two to find some of these big holes, I'm sure there's more lurking under the surface. I'd consider it to at this point be more of an engineering demo that this approach is feasible and tractable, but not a production ready language that I'd want to ship code in.
On the muddling the discourse, I'm not on twitter and don't engage there, so I don't have an opinion on that, but I did come across https://news.ycombinator.com/item?id=49044561 recently, and I just don't see how the author can make such bold claims while examples like the one Steve provided above are still in the language. Corrupting memory in Fil-C is still easy, type confusion is still easy, intra-object overflows are still easy. Fil-C prevents a range of classes of bugs from being exploitable, but it doesn't stop the bugs from happening.
> it promotes an "us vs them" mindset, instead of a "we're all trying to improve memory safety" mindset
But you did the same thing when, on the spectrum that ranges from C to ATS, with Zig, Rust, and Java somewhere in the middle (though all closer to C than to ATS), you declared the exact compromise that Rust makes "table stakes"! [1]
Zig improves on C's memory safety when it comes to spatial safety, possibly the more impactful kind, so it, too, could be part of the "we're all trying to improve memory safety", yet you exclude it.
You're trying to draw some hard line that passes exactly between Rust and Zig on the C to ATS spectrum, and I'm trying to say that that line isn't there (your attempt at a definition of delineating safe and unsafe code also applies to C). Obviously, C, Zig, Rust, Java, and ATS all make very different tradeoffs, all of which may be more or less attractive to different people and in different circumstances, but there is no sharp line, at least not one that is meaningful enough to be "table stakes". Your personal inclinations place a premium on the things Rust offers and Zig doesn't while mine are the opposite, but I make no claim to universality.
I'm happy to accept that not everyone shares my aesthetics and can understand why some people prefer Rust, but those claims to or hints at universality annoy me (as they did when they were made by Haskellers, and I actually find Haskell's aesthetics quite pleasing), as they are simply unsupported. I've spent a lot of time studying formal methods and software correctness in general (https://pron.github.io) and if there's one thing we know in that field is that things are never that simple (and, bringing this back to this posts topic, even something like incremental compilation can contribute to program correctness).
(Now, you may argue that you're only talking about "memory safety" and not correctness in general, but what gives memory safety value is that violations are causes of many dangerous vulnerabilities; but once, say, Java eliminates all of them, 100% of bugs/vulnerability - which are still numerous - will be caused by other problems, all potentially avoidable with ATS, so why isn't ATS table stakes? Of course, the answer is cost, but all the languages on the spectrum differ in their costs.)
[1]: I assume that you meant Rust's compromise, because you implied that Zig doesn't pass that bar but Rust does.
> But you did the same thing when,
I do not go around posting "omg Rust is SO MUCH BETTER than zig or fil-c, which are TRASH." I talk about engineering tradeoffs, and what matters to me personally. I do not say "if you use Zig, you are a bad person." I am not saying that any comparison is bad. I am saying that the way that the comparison is presented is bad. That is different.
> you declared the exact compromise that Rust makes "table stakes"
Table stakes for me.
> Zig improves on C's memory safety when it comes to spatial safety,
I agree that it's an improvement on C!
> yet you excluded it.
I said that it is not pursuing a design that I personally find compelling enough to use to write software. That doesn't mean that I think it's worthless. This whole thing started off with me talking about how much I respect the Zig project! Yet you're trying to turn this into something where I'm talking shit. I presented a specific technical tradeoff that is important to me. That is very different.
> You're trying to draw some hard line on a spectrum that passes exactly between Rust and Zig, and I'm trying to say that that line isn't there (your attempt at a definition of delineating safe and unsafe code also applies to C)
I don't believe you've shown that. And my "attempt" does apply to C: it fails the bar, because it does not delineate between a safe subset and an unsafe superset.
> you may argue that you're only talking about "memory safety" and not correctness in general,
I am in fact talking about "memory safety" and have been this whole time, yes.
> what gives memory safety value is that violations are causes of many dangerous vulnerabilities; but once, say, Java eliminates all of them, 100% of bugs/vulnerability - which are still numerous - will be caused by other problems, all potentially avoidable with ATS, so why isn't ATS table stakes?
This is just an entirely different question. Yes, there are other forms of safety that are important too. That's just not what we're talking about here.
> Table stakes for me.
Ok, so if you meant "table stakes" as an expression of a personal preference and suitability to the programs you write without making an unsupported universal claim such as "this leads to better correctness" or "the price is almost always worth it" then we're good :)
> Yes, there are other forms of safety that are important too.
The thing is that they can be at least equally important, and some affordances for memory safety could potentially _harm_ them. To me, Rust offers little safety in the programs I want to write in a low-level language, but the price it charges in language complexity and implicitness ends up in a negative balance (I can't prove it, of course; as I said, software correctness is very complicated, and some of the greatest researchers in the field were proven wrong on how to best achieve it).
There's a distinction between claiming something is objectively better, which is what Fil-C claims with respect to its "idea" about memory safety compared to Rust... and claiming a personal preference for one approach versus another approach, which is what OP is saying about their own personal preference about how Zig reduces errors compared to how Rust reduces errors.
It is absolutely possible that one language might actually have an objectively better approach to memory safety than another, and in such cases it is usually possible to argue for this using sound technical or empirical arguments. But the way the author of Fil-C presents their arguments it often comes across in a kind of antagonistic manner, like he has a chip on his shoulder.
> But the way the author of Fil-C presents their arguments it often comes across in a kind of antagonistic manner, like he has a chip on his shoulder.
You may well be right. I've yet to learn about it, but I'm planning to.
I’ve been watching this debate online and in my opinion both sides are guilty. Fil is intentionally trying to be funny or at least “interesting “ when he makes his points and I, for one, enjoy his humor, which includes having a go at Rust and other languages. It seems Rust people just can’t take a little criticism, even when it comes from a clearly trolling language! Yes it’s true Rust has an escape hatch, and we’ve seen serious memory safety bugs due to unsafe Rust in the wild. Fil-C does not have one, what you post seems to be internal or even temporary stuff given the author clearly has a goal of not providing one? I would say you and others need to just relax and not treat all and every Rust criticism as an offense to you.
Maybe I'm just old, but I want to focus on engineering outcomes, not "trolling." If that means "can't take a joke," that's fine, but also "haha I'm just joking" is often what people use to try and hide behind their actual intentions.
I don't even work on Rust anymore, and in fact started this thread with a criticism of Rust. There are lots of good criticisms of Rust. There is a difference between "this criticism isn't good" and "every criticism is an offense."
Yes! I think Fil is great so far but I think as he gets a bigger audience he should, well, consider that and focus on clarity a little more than humor. You can see Andrew’s growth in that respect.
Rust folks, this whole thing is a thread about Zig’s new feature - not even a memory safety-related feature! - and we cannot spend the whole damn time talking about Rust.
Steve, even you - I don’t believe I have ever seen you say an unkind word. But have you considered that it may be unkind to have written more than half of the words on a thread about a Zig performance feature?
> But have you considered that it may be unkind to have written more than half of the words on a thread about a Zig performance feature?
Inherently? No! I commented specifically because I was really glad to see this post. This work that Zig is doing is very good, and I wanted to call that out, in part specifically because I am on "the other side" in whatever sense that is. Why would it be unkind for kind words to be coming from me?
I do see that you often have kind words for Zig and some attempt to more precisely define the differences.
What I mean is a bit different though, it’s that these arguments you get drawn into end up drowning out any real discussion of Zig’s progress. I don’t think that’s your intent but it is frustrating. I should be clear, I don’t think it’s wrong for you to defend yourself from accusations etc., I just wish it didn’t look like this.
I wonder how much better HN would be if they took a page from other forum systems that said “you know what, this whole branch of stuff should be moved over here and renamed so the original topic can move on”.
Sorry, all this may be unhelpful, I don’t know where the line should be, I’m just thinking out loud about the problem.