Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I still have yet to understand if Zig actually provides memory safety in the same manner Rust does or not, it seems like its another C like language without memory safety? Or did I happen to miss that bit?


> I still have yet to understand if Zig actually provides memory safety in the same manner Rust does or not

It doesn't.


It doesn't, but it puts a RED BANNER in every place where memory is allocated, and you need to explicitly pass an allocator of your choosing whenever memory is allocated. It also contains no hidden allocations or function calls. So it forces you to think about your memory allocation patterns, and also includes some tools to catch memory leaks [1].

1: https://ziglang.org/learn/samples/#memory-leak-detection


That doesn't really help.


Just as your comment :-p


If you didn't find "thinking about memory allocations doesn't help much with memory safety" useful I don't really have much more to share.


So its C with nicer macros and maybe some more sensible ways of dealing with arrays and such?

For me that's not enough to move the needle.


It's more than that in that it has a sane syntax and module and build system.

I'm a Rust person by both day job and hobby, but I can see the niche Zig is in as being quite useful in particular for embedded work.

One thing it definitely has over Rust is way better support for explicit per-structure allocator/allocation management. The allocator_api stuff in Rust has just been sitting unstable for years at this point, while Zig shipped with support for this immediately. Which is key for work in all sorts of systems level contexts.

Probably the language I want is somewhere between the two.


I mean I really have a hard time going backwards on the whole idea of lifetimes and send/sync type traits which force escape hatches if you want to do something idiotic. The key piece here is, any project of significant size and usage, someone will try to do something stupid. Rust makes the stupid so obvious with unsafe blocks and annotations, that reviewing code from some random person on the internet is made easier. The tooling made the task of reviewing code mostly limited to the code being changed rather than the whole project.

In C/C++ land you need to consider not only the code being changed but also the entire code base and how that code is used. It's not enough to look at the diff. You need to understand the context.

Not to say that isn't also true with Rust to some degree, but the degree is usually in terms of logic and less "is some pointer/reference going to cause a race/segfault/buffer xflow?"

The langauge itself doesn't define allocation, Box is in the stdlib and this allows for nostd libraries to deal with things as I guess most of us might expect I'd think. It would be cool to allow for a global allocator though to enable std on more platforms with better semantics, no disagreement there.



In effect this answers my question pretty well, the answer seems to be a resounding "No there's no memory safety" at least in the practical or realistic sense that results in few to no memory lifetime errors.


Zig provides tools to avoid memory related bugs, but it doesn't enforce it via compile time checked RAII/burrow checking etc.

Nullability (and error handling) is compile time checked though.


To me the biggest win in Rust is just that, the memory related bugs are nearly non-existent. Throw in a group of people working on something and this makes the project move just so much faster in both review time and time hunting bugs.


IMHO the real big win w/ Rust is not so just memory safety from (some) leaks or segfaults but the fact that the borrow checker rules apply also to concurrency related scenarios and so race conditions and deadlocks become much more difficult to accidentally produce.

Developers can generally be taught to handle memory ownership issues fairly well, esp when helped out with RAII smart pointers, etc.

But in my experience when you throw concurrency in, even very high quality engineers tend to stumble and accidentally introduce race conditions.

In this respect Rust has something over the garbage collected languages, too. Because Java, Go, JS, C#, etc will effectively shield you from "use after free" but they will absolutely not stop you from passing an unlocked, etc. reference to another thread and screwing yourself up royally; and Rust will.


Zig gives buffer overflow safety and null pointer safety but not use after free, double free, or stack pointer escape safety.

Zig also gives you memory exhaustion safety which rust does not.


>Zig gives buffer overflow safety and null pointer safety but not use after free, double free

It can provide the latter two through the use of the `GeneralPurposeAllocator`, which tracks UAF and double free.

Stack pointer escape safety is being actively researched (there are a few tracking issues, see [1]). I'm personally interested in this, I've written a decent-ish amount of Zig for toy/personal projects, and anecdotally stack pointer escape is the only type of memory error that has bitten me more than once (though to be fair, one of these cases was calling into a C API, so even Rust wouldn't have helped).

More broadly, the ability to catch all forms of UB in Debug/safety builds is an accepted proposal [2], though whether or not it will be possible in practice is a different (and interesting!) question.

[1]: https://github.com/ziglang/zig/issues/2646

[2]: https://github.com/ziglang/zig/issues/2301


The way `GeneralPurposeAllocator` works is kinda scary though, since it may result in whole memory pages used only for very small allocations, effectively multiplying the memory usage of the program. Also kinda goes against the memory exhaustion safety, since now you're more likely to exhaust memory.


Yeah, I don't think it's right to say Zig doesn't have use-after-free and double-free safety. If you want that, you can just use a general purpose allocator. Note that you can't forget to choose an allocator since they are explicit.

Is this somehow harder than, say, choosing not to use "unsafe" in Rust?

Maybe all that is missing is a linter to help enforce whatever memory-management policy you've decided on. That's not really needed for small, coherent teams, but would be important for using Zig in larger projects with multiple teams and/or disparate individual contributors. (Perhaps such a thing exists and I just don't know about it.)

You might also be able to use an arena allocator where free is a no-op. That has different tradeoffs, but is also safe for use-after-free and double-free.

As you say, stack escape is the main thing where Zig doesn't have a good memory-safety story yet (at least not that I've heard). I guess there are a few others that concern me when I see them on a list, though I haven't hit them in real life.


Static analysis at the IR level would be awesome. It could catch use-undefined, stack escape, and probably uaf/df as well so you don't have to lean on gpa's (potentially expensive) tricks. Plus there are times when you maybe don't want to use page allocator.

As an aside. I'm not certain I understand how double free is memory unsafe (in the sense of "causing vulnerabilities")


A double free breaks invariants for the memory allocator. For example, the freed memory may have been handed out to someone else and if you free it again, it will be marked as unused even though that code relies on their stuff being available. One very classic way of exploiting a double-free is a sequence like this happens:

1. Some code allocates memory.

2. The code frees the memory, but keeps a stale reference to it around. It is marked as unused by the allocator.

3. Some other code allocates memory. Maybe it's reading the password file off of disk. The allocator has some unused memory lying around so it hands it out–but it turns out that this is actually just a reuse of the buffer from earlier. It is now marked as "in use" again by the allocator.

4. The code from earlier has a bug and frees the allocation again. This means that the allocation is now marked as "unused".

5. Another allocation request hands out this memory again. Maybe it's a buffer for user input? Well, it's been scribbled all over with other data now.

6. Someone asks to authenticate and the password checking code gets called. It has the password right here to check against…oh, wait, that memory got freed out from under it and overwritten with some attacker-controlled content!


> I'm not certain I understand how double free is memory unsafe (in the sense of "causing vulnerabilities")

Perhaps there are some allocators where doing that hits UB. UB in memory allocation is probably always a memory safety issue. I would say if your code accepts any allocators where double-free could be UB then you've got a safety issue.


C has use-after-free and double-free safety if you patch out free. Not really a solution, is it?


What do you think the difference is between “patching out free” and allocators as a first-class feature? I’ll bet you can think of a few rather significant ones if you try.


Zig has custom allocators as a first-class feature. It does not have memory safety as a first-class feature.


An allocator that does heap quarantining at the page level is not a "general purpose allocator". It is a debug tool.


Is there a memory exhaustion scenario where Rust does something other than panic?


Rust-the-language doesn't do any dynamic allocation, so it doesn't exhaust memory at all. The Rust stdlib currently guarantees an abort on OOM, but in the future this will be changed to a panic (which you can always configure to be an abort if you want). Both of these behaviors are memory-safe (it's unclear what "memory exhaustion safety" is referring to).


Also to clarify: there are platforms (like linux) which de facto have nonfailable allocation, if you run out of memory the system will oom kill your process, and neither zig nor rust will save you from that.

I believe In practice the most common place where failable OOM is a big deal is in embedded systems programming


No language can save you from OOMs, but because Zig pervasively uses explicit memory allocation, it makes it easy to greatly mitigate OOM risks by front-loading all the fallibility:

1. Calculate and allocate all your required memory immediately upon process startup (including memory from OS resources like sockets)

2. Call mlockall to prevent the pages from being swapped out

3. Write "-1000" to /proc/self/oom_score_adj to avoid the OOM killer

4. Use your preallocated memory as a pool for all further allocations

With the above approach, the application has full control over how to handle application-level OOMs (e.g. applying backpressure to connecting clients, or shrinking non-critical caches) once it is past the start-up stage.


> memory exhaustion safety

Sure this more like DDOS mitigation, so it memory related safety, not memory safety.


You can use the fallible allocation APIs such as `Vec::try_reserve`


What do you mean by memory exhaustion safety?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: