From the table:<p>> null pointer dereference ... [C] none; [Zig] runtime; [Rust] runtime<p>Assuming this is talking normal "safe" Rust, I think I disagree with this. The Rust analogue of a pointer in safe code is a reference, not a Rust pointer, and these can't null at all. You could use an Option<> of a reference, and Rust will internally use null to represent the None (empty) case, but an attept to use the option without checking for None will result in an error at compile time, not runtime. Yes you could convert that into a runtime error, but if it was an error condition for that variable to be None then (depending on the context) you could choose not to use an Option at all and then it would be a compile-time error at the call site to attempt to put None into it.<p>I don't think I understand what is meant by "type confusion". Surely this would also cause compile-time errors? Even C++ would give compile time errors for this unless you use a cast! (C, unlike C++, lets you implicitly convert from void* to any other pointer type so you don't need a cast to get pointer confusion.) Could someone think of an example of what might be meant here, and how it would cause a runtime error?
One thing that's important to note is that Zig in general enforces much more correctness than C and also has much better stdlib APIs. IME many memory corruption issues in C (and C++) are actually "secondary effects" of C's and C++'s general "sloppiness" and bad stdlib APIs.<p>One thing I found where Zig is currently worse than C compilers: returning a pointer to a stack variable generates a warning in "modern" C and C++ compilers, while Zig lets this slip through. I hope that "trivial" things like this will be fixed on the way to 1.0
One thing that's important to emphasise: sound safety, i.e. using a safe language, is <i>no one's</i> goal; rather, it is a means to end. What people want is correct <i>programs</i>. The question is, then, does a language help write correct programs?<p>Ensuring safety from important classes of bugs with sound guarantees is one way to help write correct programs, and both Zig and Rust use it; ensuring safety from from important classes of bugs with sound guarantees based on runtime checks is another way, and both Zig and Rust do it, too; a simple language that's easy to understand and analyse is another very important way to help write correct programs, and both Zig and Rust try to be simpler than their predecessors; making it easy to write tests and run them frequently is another way to get more correct programs that both languages try to employ. Both languages drastically differ in the use of those techniques from either C or C++ because they are both languages that put a very strong emphasise writing correct programs, but they also differ a lot from each other in how they balance those techniques.<p>It is impossible to tell without careful empirical research which helps write correct programs more than the other, and it is also possible that different people find it easier to write correct programs in either Zig or Rust. Rust certainly provides stronger guarantees that prevent temporal memory bugs than Zig, so let's assume Rust programs will contain zero, and a Zig program will contain more than zero but much less than C or C++. But that delta is insufficient to determine that Rust's balance of techniques reduces more bugs overall.<p>Also, Zig already has decent checks for use-after-free, and they'll get better, and not having uninitialised memory is also very easy to do (and verify) in Zig, despite there not being any checks. Even if, like other runtime checks, it is turned off in production, it still helps catch errors in that category.
It's important to clarify that memory safety is only one aspect of writing safe, secure software.<p>To this list then I would also want to add and compare: OOM-safety under overload conditions, and fine-grained error handling safety, in particular because error handling tends to be one of the leading causes of faults in distributed systems [1].<p>To be fair, I was surprised that Rust did not have checked arithmetic on by default and that this needs to be turned on via compiler setting or linted against. The presence of integer overflow in a program can facilitate a whole range of exploits, even with memory safety.<p>[1] - <a href="https://www.eecg.utoronto.ca/~yuan/papers/failure_analysis_osdi14.pdf" rel="nofollow">https://www.eecg.utoronto.ca/~yuan/papers/failure_analysis_o...</a>
Someone on Reddit added Nim to the table, which I found interesting:<p><a href="https://uploads.peterme.net/nimsafe.html" rel="nofollow">https://uploads.peterme.net/nimsafe.html</a><p>(Source: <a href="https://www.reddit.com/r/nim/comments/maj1lz/nim_safety_in_comparision_to_c_zig_and_rust/" rel="nofollow">https://www.reddit.com/r/nim/comments/maj1lz/nim_safety_in_c...</a>)
I don't have much experience with Zig, but one thing that stuck out to me was that I was able to build for RISC-V with a one-liner. I didn't have to change or do anything at all to make this happen. That's so cool.<p>In contrast, I have yet to be able to build any RISC-V binaries with Rust. It just doesn't work. Sure, I could see some potential things like writing a custom JSON to describe the env and maybe build using a cross-compilation toolchain. But after a certain amount of time and no answers, it was not worth my time anymore.<p><a href="https://stackoverflow.com/questions/64308644/rust-unable-to-build-64-bit-risc-v-binary" rel="nofollow">https://stackoverflow.com/questions/64308644/rust-unable-to-...</a><p>If you think you have the answer ^
The recommended way to deal with use-after-free and double-free in the language is to do it in test. You can pretty trivially get "asan"-like behaviours out of the zig std library. A good demonstration is here: <a href="https://www.youtube.com/watch?v=4nVhByP-npU&t=3h12m" rel="nofollow">https://www.youtube.com/watch?v=4nVhByP-npU&t=3h12m</a><p>I kind of like this philosophy, because in a sly way it's a carrot to get you to write tests. Come for the memory safety, stay for the robustness.<p>As a bonus, the beginning 2 hours of the video is a fantastic and honest discussion about the role of emotional empathy in tech communities and tech employment (while also acknowledging that it is possible to be an asshole and deliver good tech).
It’s interesting to note and probably underappreciated how many of these safety issues are addressed simply by having a garbage collector — eliminating manual memory management prevents all of these kinds of bugs except for data races. Of course there are situations where you cannot afford a GC, but for how many programs is avoiding a GC worth all the additional language complexity? Preventing data races is no small thing, but this observation certainly suggests that the approach of GC + tasks & channels + a good race detector is more powerful than it is commonly given credit for — think about how much user-facing language complexity it replaces. This sounds like a pitch for Go, and to some extent it is, but Julia takes very much the same approach for the same reasons.
I think this is a roughly fair assesment, but I also think it's important to contextualize memory safety. Ultimately, the goal here is to produce /correct/ software. Memory safety is a subset of this, but there are other aspects to correctness as well.<p>I really like zig's approach of explicitness and fast iteration cycles. Fast compile times and the very flexible build system makes me hopeful for a really slick workflow for embedded development,where zig code can be used to deploy and test as well. For my own use I think it's a clear win.<p>On the hand, the amount of damage poorly architected zig code can cause is about as large as for poor c code. For typical enterprise code the rust compiler will make sure that many bad decisions will not even compile. There's still a risk of towering abstractions, but at least I could avoid spending as much time debugging hideous race conditions.
Just somewhat related to the article but I think one thing which is often misunderstood about rust's is it's borrow checker.<p>The borrow checker is <i>not</i> about memory safety but about aliasing guaranteed.<p>It just happen that combining this with deterministic destructors (/RAII) happend to enable reliable "automatic-manual-memory-management" (or however to callit).<p>And combining it with some clever auto traits (Send/Sync) happen to prevent data races (if no unsafe is used, like always).<p>But the benefits are not limited to just that. Not just memory-resource management but also other resource management profits from this design.<p>Similar while Send/Sync is about multi threaded data race prevention there are also problems in single threaded patterns which are quite similar to that e.g. "racing" between iterating a collection and changing it in the body of the iteration, and the aliasing guarantees make sure you don't have such problems either.<p>Similar rusts main pointer type (`&`) does not only provide compiler time non-null guarantees but also provides compiler time guarantees about how the data can be accessed (dereferencable, writeable etc.).<p>And then there is the choice to use the type system to prevent application logic bugs in many ways.<p>So the bullet points in the table miss many dimensions .<p>But then zig is still a grate language, but trying to convince people that it's good enough by telling them that not reusing allocations seem to not be the best way.<p>Instead look at arguments why people still use C today (not C++!) what they conceptually like about it and you might realize many of the parts still apply to Zig.<p>Honestly Zig seems to be a grate choice for webasm or similar sandboxed systems where the potential damage of use-after free or double frees can be <i>massively</i> reduced.
I think, it is very important, that people started to actually discuss and compare different approaches to safety, instead of just saying that since Rust is safer, we should throw out all of c/c++ code and rewrite everything in rust.
> <i>Temporal memory safety and data race safety. [...] Unique to rust. [...] add a significant amount of complexity to the language.</i><p>Rust seems to be a very complex language. Is all that complexity essential to providing memory safety without GC? Or would it be possible to have a significantly simpler language that is equally safe? A language that’s “safe & C-like” compared to Rust’s “safe & C++-like”?
Interesting comparison. Long term we badly need something to replace C (or at least minimize its usage drastically), so perfect should not be the enemy of good.<p>I hope something like Zig gets widespread adoption, including in embedded/IoT/automotive environments. Especially automotive. We're moving more and more life-and-death scenario-type tools into software.
The word "none" in this blog post is misleading, for the rows use after free, double free, and uninitialized memory. Criticism is of course welcome but let's make sure we get all the facts on the table so we're not arguing a straw man.<p>Copied here [from lobsters](<a href="https://lobste.rs/s/v5y4jb/how_safe_is_zig#c_vddk9j" rel="nofollow">https://lobste.rs/s/v5y4jb/how_safe_is_zig#c_vddk9j</a>):<p>With regards to use after free and double free, this is solved in practice for heap allocations. The basic building block of heap allocation, page_allocator, uses a global, atomic, monotonically increasing address hint to mmap, ensuring that virtual address pages are not reused until the entire virtual address space has been exhausted. In practice, this is a very long time for 64-bit applications. The standard library GeneralPurposeAllocator in safe build modes follows a similar strategy for large allocations and for small allocations, does not re-use slots. Similarly, an ArenaAllocator backed by page_allocator does not re-use any virtual addresses.<p>This covers all the use cases of heap allocation, and so I think it’s worth making the safety table a bit more detailed to take this scenario into account. Additionally, as far as stack allocations go, there is a plan to do escape analysis and add this (optional) safety for stack allocations as well.<p>As far as initialized memory goes, zig forces you to initialize all variable declarations. So an uninitialized memory has the word undefined in it. And in safe build modes, this writes 0xaa bytes to the memory. This is not enough to be considered “safe” but I think it’s enough that the word “none” is not quite correct.<p>As for data races, this is an area where Rust completely crushes Zig in terms of safety, hands down.<p>I do want to note that the safety story in zig is [under active development](<a href="https://github.com/ziglang/zig/projects/3" rel="nofollow">https://github.com/ziglang/zig/projects/3</a>) and will be worth checking back in on in a year or two and see what has changed :)
The D programming language:<p>out-of-bounds heap read/write: runtime, some cases at compile time<p>null pointer dereference: relies on hardware protection<p>type confusion : compile time<p>integer overflow: wrap-around semantics<p>use after free: prototype protection in @live functions, not a problem when GC is used<p>double free: prototype protection in @live functions, not a problem when GC is used<p>invalid stack read/write: compile time<p>uninitialized memory: compile time<p>data race: read/write to shared memory can only be done via library functions
I think if Zig wanted to, it could introduce lightweight linear types using the concept of proof variables and interleaving from ATS. Since resource management is explicit in Zig anyways, there's not much additional overhead in "consuming" proof values to signal that you've dealt with a resource.
> null pointer dereference (C)none (Zig)runtime (Rust)runtime<p>Not really rusts pointers are the `&`/`&mut` references which are compiler time proven to be not just non null but actually differentiable and potentially writable in the given context. Which are MUCH stronger guarantees then just "not null".<p>> 2. only when using tagged unions<p>Which in rust are <i>the</i> default types, unions didn't exist for quite a while and require the use of unsafe making them heavily discouraged to be used.<p>Besides that it's not that rust enums are tagged unions where you at runtime check a tag and then access them, or where you "panic/throw an exception" when you try to access the wrong type, but incoperated into the type system and language given quite a different experience to classical tagged unions.<p>Lastly type confusions applies to more then just "tagged union style access" but also subtype stile access in which case rust can use trait objects instead of sum types.<p>Besides that many of the ways listed zig can archive more safety (weather applicable or not) are also applicable to C. And some of the checks Zig do can be "somewhat" archived in C too by combining non standard compiler options and code analysis tools.<p>Don't get me wrong. Zig is a very interesting language and I would argue the spiritual successor of C in how it's designed.<p>Still I guess the main ways to add more safety (and similar) to languages like Zig (or C) is to compile them to WebAsm. The module isolation while still being able to call other functions without to much overhead which can be archived with WebAsm might lead to quite interesting trends in the future.
Safety for me is confidence to use the thing. For me in my own code, but also others on my team that may work on this code.<p>I mostly have experience building things in GC languages. But with Rust I managed to safely use [1]:<p>- stack references in threads<p>- kept mmap references alive until threads finish work<p>- zero copy xml parsing (from mmaped data!)<p>- SSE/AVX enabled searching<p>The Rust language empowered me to do these things with a high degree of confidence. Not one segfault or core dump, just lots of compiler errors.<p>I played with Zig. Admittedly, the small ecosystem aspect is something all languages go thru, and it would be a better experience with a Zig specific libraries. But Zig doesn't empower library authors to make a large category of bugs impossible, and leaves it to documentation. This is like C, I don't have enough confidence in myself to use it.<p>Brilliant people are building powerful, safe-ish, reusable libraries in Rust. For mere mortals like me, this is Awesome.<p>[1]: <a href="https://gist.github.com/daaku/58557e2545612df8f40b13b66b7d3bd0" rel="nofollow">https://gist.github.com/daaku/58557e2545612df8f40b13b66b7d3b...</a>
A problem for Zig is the combination of unsigned overflow UB, implicit widening and always performing operations on the type size of the operands.<p>For example, let’s say you have `a = b + c` with b/c being i8 and a i32. This calculation is first performed as an 8 bit add, then extended to 32 bits. This is true for both Rust and Zig, but Rust requires an explicit cast to widen the result of `b + c`, making it obvious that an extension happens and that `b + c` is not performed in 32 bits. In Zig there is no such indication- you need to look up the definition of b and c. Other problems occur as well, that both C and Rust avoid in their own ways. Hopefully Zig can improve this situation.<p>See more here: <a href="https://c3.handmade.network/blogs/p/7651-overflow_trapping_in_practice#23988" rel="nofollow">https://c3.handmade.network/blogs/p/7651-overflow_trapping_i...</a>
I cannot live without RAII. Zig is just a no-go for me without automatic resource cleanup. A lot of people forget cleanup and destroy safety without RAII.
<a href="https://github.com/ziglang/zig/issues/782" rel="nofollow">https://github.com/ziglang/zig/issues/782</a><p>Zig is apparently a PL for folks with <i>perfect</i> memory who never make mistakes like those described in that ticket. "Lesser" programmers like me can choose another language.
I don't know Zig, at all, but I do know Rust and C, and I know what a UAF bug looks like. What does a UAF bug look like in Zig? That a modern memory-safe language could be vulnerable to the C-language UAF pattern is a surprising claim.
Safety without context is Apples-to-Oranges. I could write a compiler which simply rejects any possible source-text you send it and check "compile time" for every box and offer "greater safety guarantees" than Rust.
The matrix is incomplete:
What about use after returns?
Memory leaks?
Cycles?
Stack under/over-flows<p>And more from
<a href="https://github.com/google/sanitizers/wiki/AddressSanitizerComparisonOfMemoryTools" rel="nofollow">https://github.com/google/sanitizers/wiki/AddressSanitizerCo...</a>
> Sometimes we might also just choose the bear the cost. For systems with low risk profiles (eg internal software that is never exposed to hostile input) we might decide that debugging the occasional use-after-free is preferable to adding development friction.<p>Zig vs Rust vs C aside, this cannot be a serious position for a software developer to take in 2021 CE.
From the very top of the article:<p>> <i>For various common safety issues, we can look at protections that are present in software as it is typically shipped (ie excluding tools like AddressSanitizer that are not recommended for production use):</i><p>...with a link to a five year old opinion post to oss-security as a reference for "not recommended".<p>To wit: "There is other stuff in this space that might be relevant, but I don't want to talk about it so I'll just make up a reason. Moving on..."<p>That kind of logic tells me instantly that this is spin and not a serious analysis. I know next to nothing about Zig, but I know I shouldn't trust this post to tell me about it.
Only as safe as the creator decides. Andrew has shot down discussions about DOS vulnerabilities in the standard library and the cult-like discord community loves to dig on anyone who brings such things up.<p>I will be staying away from Zig exactly for that purpose. Great idea but I can't get behind a maintainer that adamantly refuses to even discuss proper, safe standard library design.<p>EDIT: Yep, the HN crowd tends to be the same. Downvote me all you want please :) We'll see over time.