[0]: https://www.scattered-thoughts.net/writing/how-safe-is-zig/
[0]: https://www.scattered-thoughts.net/writing/how-safe-is-zig/
Put another way, the answer to your question is because even if program correctness were the only value that matters to anyone who does low level programming (and it isn't), there's no agreement sound memory safety in the language -- at least the way Rust does it -- is the best way to achieve correctness. Put yet another way, the answer is: for the same reason people would want to use a language with memory safety -- because they think it helps them achieve their goals better.
Zig does lack a general solution for temporal memory safety. That is a downside. But it can still have that safety in ReleaseSafe mode, at least - for example, by not reusing memory addresses. That adds overhead, which might rule Zig out in certain areas, but not others.
Also, while in many domains safety should be the #1 factor when when choosing a language (like building a web browser), that's not universally true. Other factors exist.
I don't believe this is true. Zig has pointers to unknown numbers of items, which don't seem bounds checked: https://ziglang.org/documentation/master/#Pointers
They prefer slices idiomatically, but that's not "full spatial memory safety". C also prefers you to pass the length of every array whenever you pass a pointer to it, in that correct code must do this. But the entire point of language memory safety is that we don't trust programmers to consistently do the right thing.
You might be able to say something like "Zig minus features X, Y, and Z has full spatial memory safety". I'd be interested to see what features those are: it looks like at a minimum you would have to get rid of multi-element pointers and extern unions.
> But it can still have that safety in ReleaseSafe mode, at least - for example, by not reusing memory addresses.
The overhead is extremely high, because it leaks an entire 4kB page if a single allocation from that page is still alive. In the worst case, it's equivalent to rounding every allocation up to 4kB. If you're OK with that overhead, you could just link in the Boehm GC and get the same safety with less memory usage, and as an added benefit you wouldn't have to call free anymore.
> Also, while in many domains safety should be the #1 factor when when choosing a language (like building a web browser), that's not universally true. Other factors exist.
In the vast majority of those domains, you could just use a GC'd language. I don't see much room for a new language that isn't memory-safe in 2022.
[*] is a syntactically delineated unsafe feature. Rust has the same thing. It's like saying Rust prefers safety, but doesn't enforce it because it has unsafe features; same goes for Haskell.
> I don't see much room for a new language that isn't memory-safe in 2022.
That statement [1] is about about as silly as "I don't see much room for a new language in 2022 that is obviously too complex to see wide adoption." If a language becomes popular then obviously there's room for it, and if it doesn't, then the question is moot. Maybe what you mean is that you don't think Zig would ever become popular -- and you may well be right -- but you work on a language that is subject to similar scepticism and doubt.
Both Rust and Zig have some huge challenges to overcome if either one of them is to have "room", and I wouldn't bet on their chances, but it's good to have some widely different approaches and see which, if any of them, turns out to have "much room".
[1]: Even disregarding the hard question over which of Zig or Rust make it easier to write correct programs, which could go either way; that sound memory safety is a better path to correctness than a balance of less soundness combined with simplicity is your opinion, and few would claim that Rust's flavour of safety doesn't come at a cost.
In Rust all of those features are delineated by unsafe. For one, you can disable unsafe with a compiler switch; Zig has no such equivalent feature.
Moreover, we shouldn't assume that Zig pointers are the only feature that breaks spatial memory safety. It was simply the first one I found after like 2 minutes of looking through the docs. After like 5 more minutes I found another: extern unions. Just now I found another: sentinel-terminated pointers, since you could delete the sentinel.
It feels a bit like these arguments are going like "Zig is spatially memory safe!" "Well, what about X?" "OK, other than X, Zig is spatially memory safe!" "What about Y?" "OK, other than X and Y, Zig is spatially memory safe!" At this point the burden of proof isn't on me anymore.
It's obvious that full spatial memory safety just isn't a design goal of Zig. It seems like Zig's goal is simply to add tools, like slices, that reduce memory errors. Which is the same goal that, for example, the C++ STL has. It's a great goal, but it shouldn't be confused with Rust's goal.
> [1]: Even disregarding the hard question over which of Zig or Rust make it easier to write correct programs, which could go either way; that sound memory safety is a better path to correctness than a balance of less soundness combined with simplicity is your opinion
It's my opinion in the same sense that it's my opinion that wearing seatbelts results in fewer deaths on the road. The idea that memory-safe programming languages improves real-world safety is backed up by decades of experience. Anyone arguing otherwise has a massive burden of proof. And the arguments I've seen by people arguing that you don't need temporal memory safety are weak. The idea that a simple language that doesn't have ironclad safety guarantees reduces errors over a more complex language with those guarantees sounds nice in theory, but it hasn't actually turned out that way in practice.
You're right on each of those points. But (AFAIK) all those types are explicitly there for C interop. When interfacing with C, yes, Zig will inherit much of C's unsafety. But the big picture is that
1. Idiomatic Zig should not use those things, and
2. Those things are (IIUC) mechanically-identifiable at the type level. A compiler flag could warn about them. You can check if a codebase uses them, and find out exactly where. No such thing is possible for C.
Maybe you'd prefer that an unsafe block be required to be placed around each of them, but in the end that's a matter of notation. In theory, Zig could decide to mandate such explicit unsafe blocks, but C couldn't - there isn't a useful safe subset to delineated.
I could be wrong, however. Possibly the Zig community will decide that say sentinel-terminated pointers are good to use in general (and not just for C interop). And maybe I've missed something and those types cannot be discovered mechanically for auditing purposes. If either of those is the case then I'd agree Zig lacks spatial memory safety.
(Btw, you seem downvoted atm. Not me, of course - there's probably no one else I respect more on the topic of memory safety than you! - and it's disappointing that people downvote comments just because they disagree.)
I'm not sure I have an opinion as strong as pcwalton here, but I do think that you can't call yourself memory safe (even on a particular dimension) if you have to delineate all of the various language features that are unsafe without having to somehow explicitly annotate it as such in the source. Or at the very least, I don't think you can say that Zig's spatial memory safety is on even footing with Rust's (assuming pcwalton's characterization of Zig's unsafe features is correct).
The annotation really is the point here, because you can pin everything (modulo bugs and OS features that permit subversion) with regard to memory safety down to an unsafe annotation somewhere. That's a powerful tool that crystalizes what it means to be a "safe API."
As I said, I don't necessarily share pcwalton's strong opinion (although I do weakly agree with him) that having an annotation like 'unsafe' is the only way to go. I do think it's possible to go the Zig route and reduce memory safety bugs. But we should be very clear eyed about the claims being made and how comparisons are drawn.
Imagine if you could run a tool on a codebase, and it statically found all the unsafe locations, and in idiomatic code there were very few of those, and you could reason about them. That would be equivalent to writing "unsafe" in the code, except you need to run the tool to "see" the annotations. That would be as good as annotations - it could prevent the same number of bugs. (Though I guess an argument could be, maybe some people forget to run the tool; fair enough.)
No such tool can exist for C or C++. But such a tool could exist for a new language like Zig and Carbon, if they design themselves in certain ways, at least for spatial memory safety. Concretely, unsafe things like raw pointers in Zig have different types than safe slices, so a tool can actually find them; and unsafe things are (AFAIK) not idiomatic either, so they'd be rare.
Otherwise, I do think you underestimate the differences between an annotation like 'unsafe' and what you can find easily only through a static analysis tool. The key bit of the 'unsafe' annotation is the concept of "safe API" that it inspires, and also gives one a vocabulary for talking about things like 'soundness.' For example, you can mark Rust functions as 'unsafe' and then document the preconditions for avoiding UB. If you didn't mark that function as 'unsafe', then we would call it unsound because there exists an input that could cause UB, but it does not necessarily result in UB for every input.
The annotation gives people a way to talk about and crystalize precisely what it means for an API to be safe for all uses. It funnels everything about memory safety down into that one concept and makes reasoning about it much much easier. Without an annotation like that, it's cumbersome or downright difficult to even talk or communicate about soundness.
I don't see any reason why modern C++ wouldn't be just as spatially memory safe as Zig is, if we're allowed to subset the language in ways more trivial than "disable the unsafe keyword". The main thing that distinguishes Zig at that point would be that it's a simpler language. That's true. But I don't actually think that makes for safer programs, empirically speaking--otherwise, C programs would be safer than C++ programs, and they generally aren't.
In other words, this isn't about theoretical subsets of a language. It's that there is an opportunity when designing a new language to use better defaults and have better idiomatic patterns. If Zig and Carbon do it properly, I think they can get to a place where they have practically no spatial memory errors in the real world. That is measurable, in principle, so we'll see how it plays out I suppose.
// g++ -O3 -D_GLIBCXX_ASSERTIONS=1 file.cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec{1, 2, 3, 4};
std::cout << vec[4];
}
MSVC provides similar functionality using the _ITERATOR_DEBUG_LEVEL directive. The point is that there is no inherent limitation that one has to use the .at() method for bounds checking STL container access.Well, you'd need to avoid arrays and pointer arithmetic, some of the most basic language primitives, but being safer than C++ (even though it is) is not Zig's only or even main differentiation from C++.
> But I don't actually think that makes for safer programs, empirically speaking--otherwise, C programs would be safer than C++ programs, and they generally aren't.
Once again you're begging the question by trying to draw similarities between C and Zig and using extrapolations that you yourself know to be wrong.
We both agree that the sweet spot for correctness is somewhere on the spectrum between C and Idris, but we really don't know more than that. No one is claiming that any language X that's simpler than another language Y will be more effective at producing correct programs, just as no one is claiming the same for any language X that can offer more sound guarantees than Y. In fact, we know that both of these statements are wrong.
What we know is that simplicity and soundness are both sometimes better for correctness but neither is always better for correctness. I.e., we know that we cannot make the extrapolations that you're making.
They're also delineated in Zig, just not with a single keyword. Extern unions, unknown-length arrays, and sentinel-terminated arrays are features, with clear syntax, used for C interop only.
> It's obvious that full spatial memory safety just isn't a design goal of Zig.
It's as much of a goal in Zig as it is in Rust; both allow circumventing it with clearly marked unsafe features.
> Which is the same goal that, for example, the C++ STL has. It's a great goal, but it shouldn't be confused with Rust's goal.
You're confusing means and end. No one's ultimate goal is having this feature or another. Both Zig and Rust have, as one primary goal, helping write correct software. They just try to achieve it in different ways. Why is that good? Because we have no idea what is the most effective way of achieving that goal, so we try different approaches.
> It's my opinion in the same sense that it's my opinion that wearing seatbelts results in fewer deaths on the road.
I don't think so, because seatbelts have few accident-related downsides, while a complex language has many.
> Anyone arguing otherwise has a massive burden of proof.
I'm not arguing against seatbelts. Anyone who claims that X leads to more correct programs than Y -- not by looking at this or that property, but as a whole -- has a massive burden of proof. I am not claiming Zig is more effective at writing correct programs than Rust. After years exploring issues in software correctness -- including with different formal methods -- my claim, which is closer to consensus than controversy, is that we simply don't know. We don't know which of Zig's or Rust's approaches leads to more software, and therefore we cannot say which of those approaches is preferable, even if correctness is the main thing we care about.
> And the arguments I've seen by people arguing that you don't need temporal memory safety are weak. The idea that a simple language that doesn't have ironclad safety guarantees reduces errors over a more complex language with those guarantees sounds nice in theory, but it hasn't actually turned out that way in practice.
We simply don't know either way. We might know that a language with ironclad guarantees leads to more correct software than C, but given that Zig is as different from C as Rust is -- and as different as they are to each other -- there's nothing that allows us to extrapolate.
Your argument amounts to, "my way is the best way", and your evidence is irrelevant extrapolations. One could then ask, if Rust's design is so great, how come, at its not-so-young age -- which is quickly nearing the age at which all languages (with the possible exception of Python) have reached or approached their all-time peak popularity -- so few people use it? Could it be that there's no "room" for it? (I'm not saying that's the case at all -- I think Rust, like Zig, is a very interesting language.) Its low popularity certainly makes claims along the lines of, "the design of this language is obviously the only design for which there's room in 2022," ring quite hollow. There's no room for anything other than an approach whose success is so uncertain? Really? Given that Rust has proven quite the opposite of an overnight success, I think some humility about its design choices is appropriate.
Zig has to make stronger and more compelling arguments as to why people should use it, instead of just reaching for Rust.
Well, `[*]` pointers and extern unions exist for C interoperability. I'm sure that Rust has to do something comparable when interfacing with C.
These pointers actually do help making C interop more safe. In C there is no distinction at the type level between a pointer to a single element and a pointer to the first element of an array, so if you at some point get it wrong, the compiler can't help you.
In Zig, if you annotate `extern` function signatures correctly, the compiler will be able to tell you when you're wrongly trying to iterate over a single-element pointer (or viceversa).
That's a good feature to have, and IMO probably the worst supporting argument for your usual complaint about Zig's existence.
Generally in memory-safe languages the corresponding features are behind some kind of "unsafe" marker: in Rust and C# they're behind "unsafe", in Java they're behind "sun.misc.Unsafe", etc. I don't see any such marker in Zig.
The critical thing, I believe, is that unsafe can be discovered and audited. Unsafe pointers have a different type in Zig, so they are separable from the rest of the language, unless I'm missing something.
The bottom line is that in a memory-safe language it should be possible to find all the unsafety and reason about it. For spatial memory safety that's not possible in C, but it is in Zig.
I don't think people say a language is unsafe if it has opt-in unsafety in controlled, non-idiomatic ways. Java, C#, Rust, etc. all have unsafe things you can do, but they are still memory-safe languages.
Specifically for Zig, I don't see a reason the compiler couldn't have an optional warning on using an unsafe pointer, so codebases can be audited for this risk, making this a controllable form of unsafety (unlike most of the unsafety in C!).
> The overhead is extremely high, because it leaks an entire 4kB page if a single allocation from that page is still alive.
In general you are right, and in a long-running process that would be the case. But consider a short-running wasm event handler: such overhead is generally not going to be significant there. So this rules out some uses cases, but not all.
> I don't see much room for a new language that isn't memory-safe in 2022.
Yeah, I agree the space for a language like Zig is limited: GC languages are the right answer for most things anyhow, as you said, and when they are not, often memory safety is crucial (like in a web browser) and I'd strongly prefer Rust over Zig there.
Still, there are use cases where Zig seems nice, like wasm event handlers that I mentioned: they're sandboxed anyhow, binaries are small, and it's nice to not have GC overhead. Zig's simplicity and fast compile times are a bonus.
Another use case are low-level things that you'd need lots of unsafe in Rust anyhow. I'm not sure if I'd prefer Rust or Zig in such a case myself. I'd prefer either over C, though.
IMHO these are the only valid reasons for unsafe code in a systems language:
1. interfacing with unsafe external code (e.g. call the kernel directly)
2. implement the language runtime/GC itself
3. access hardware capabilities not exposed in the language (e.g. SIMD, GPU, memory-mapped IO, memory barriers)
These are semi-valid to bad reasons for unsafe code:
4. generate new machine code to, e.g. implement a guest language
5. explore the performance ceiling for a very specific code pattern that would otherwise be inline asm
For Virgil, I am at reasons 1 and 2. For Wizard, I am at 4 and 5.
I'm under the impression that crashes are considered perfectly safe because they don't lead to memory unsafety. They're inconvenient, but eliminating them is more or less impossible: if you don't want stack overflows, you have to ban recursion; if you don't want integer overflow, you have to ban addition or insert checks for overflow; etc.
What areas specifically?
It seemed back when Rust was an upstart that everyone would ride its behind about how this and that feature could add runtime overhead compared to C or C++. Especially compared to C. But now people are comparing Zig and C and are saying (indirectly) that they are fine with runtime overhead in exchange for more safety? Has a shift happened?
(it's of course not a "general solution", but I wonder if something like 'tagged pointers' could be that, even without hardware support, and at some runtime cost)
Also, safety is not the only consideration when writing software right? Otherwise, all software would have to be written using formal methods [2] ... clearly overkill for many (most?) applications.
Maybe it is a bit obvious, but I think someone would pick Zig because they like the set of features that the language provides, allowing this person to write reasonably bug free code in (hopefully) shorter time than using a different language or methodology.
I think most ppl choose the language because they like the tools/ecosystem/libs available, because it aligns with previous experience/s, because of how popular it is for the kind of app they are writing, and because they agree with the authors philosophy (as opposed to choosing it based on any particular language features).
--
When is Zig the best choice?
"Freestanding" targets, perhaps?
I often write software in C to run directly on microcontrollers; no OS, no runtime.
I can see the advantage freestanding Rust would provide for e.g. 802.11 firmware. Broadcom repeatedly suffers from their choice to write the firmware for their WiFi chips in C.
… but would Rust provide any clear advantage to the programmers of a Furby or a keyboard controller?
Add to that, Rust and Go have strong corporate backing, continually get lots of shine in the media, and often are given positive spin or ratings whenever compared to other languages.
As you mentioned Nim, look how far uphill it still has to climb, after the many years (around 14 years) it has been out. TIOBE Index doesn't even have Nim in their top 50, and they aren't even on IEEE's top programming languages list (of around 60 languages). This is just as likely the fate of Zig as well. Not saying some people won't find Zig useful for particular niches, but rather it will likely never be on the level of Rust, Go, D, Object Pascal, etc... or possibly even Nim.
- easy interfacing to C libs (glfw)
- very easy and readable metaprogramming (compile time inline for over all enum variants, duck-typed and strong-typed anytype at the same time, @compileError for advanced compile checks so you can teach compiler new rules about your code instead of trying to fit your code in the extisting compiler rules)
I don't know enough about Nim to pass judgment.
Two reasons I decided to give Zig a try: The official chat channel is on IRC, instead of Discord or Slack (so the people involved care about efficiency, open standards, and avoiding trends/bandwagoning), and it has an early but promising-looking Swift UI-like cross-platform UI framework in development: https://github.com/capy-ui/capy
Or it's enthusiasm for something that's genuinely worth being excited about. Such things do come along from time to time. It's probably not a good idea to be permanently jaded, to assume that everything is zero-sum, that we can never again make progress by bending tradeoffs.
1. Rust uses Zulip (ie, not Discord or Slack),
2. Rust doesn't lack for promising-looking, declarative/reactive UI frameworks: https://iced.rs/, https://crates.io/crates/egui, for example.
The official Rust chat is on Discord.
Now, to Cyberdog's point, there is an IRC channel on libera (that I'm also a part of) and it's even bigger than Zig's channel, but it's "unofficial" and they did ask for an official one. Like, Zig's channel has the advantage of being a direct line to andrewrk which you won't get from the Rust channel.
Culturally, there's more of a "pitch in to help" and less "ask before touching". So people are mostly looking for hand-holding, "How do I?" and "Is this correct?" rather than decisions and permission.
Nothing in my comment is about a "one man" setup. Core Rust devs hang out in the Discord server. They don't hang out in the IRC channel.
>Culturally, there's more of a "pitch in to help" and less "ask before touching". So people are mostly looking for hand-holding, "How do I?" and "Is this correct?" rather than decisions and permission.
I'm not sure why you're assuming that the Zig channel is about "decisions and permission", but it's not. It's about language discussion just like the Rust IRC channel and Rust Discord are.
Computer security is a real problem that must be fully addressed at every step of the design of a system, language and tooling can help, a bit, not much.
Rust is not a "safe" language. It might be -safer- than C/C++, but only time will tell if the difference is significant.
In any serious system written in Rust, significant portions of it will have to be written in "Unsafe Rust".
You’d also have to define “serious system” and “significant portion” and if you really do mean “any,” that is, is a single counter example enough, and if not, what percentage of them would need to be so for this to be true. You’d also have to say what’s in bound or not, is it just the code you wrote for the system, is it any dependencies, etc etc etc. Does “significant” mean “large amount” or does it mean “most important”?
GhostCell is an interesting venture away from that idea. For the sorts of loopy data structures that upset the compiler, GhostCell provides a small, well-vetted, probably correct unsafe core and a safe API for accessing it that greatly reduces the amount of unsafe one should need. The core mechanism is just a higher order phantom (ghost) type parameter that gives the compiler enough information to vet that the thing you're handling was acquired properly.
The community has generally been pretty gung-ho about getting the most safe bang for your unsafe buck, and the compiler has enough bells and whistles to make many such ideas pretty seamless.
There is Go, Nim, and Rust. All pretty modern system programming languages and some of them even close to C performance.
Does Rust offer many advantages over Zig in this space?
I mostly program in C, but I've been eyeballing both for some time now.
If you are writing web apps then Rust is probably "safer". If you are writing low level code that requires obtuse memory management then I don't think you can make a blanket claim that Rust is just "safer". A lot of this nuance is not often discussed.
It sounds like you're saying rust because I don't know what other languages claim both.
Rust is an awful language. That's why. If you're using a systems programming language then chances are unsafeness isn't the problem you're most concerned with