4 ms·
How is Zig considered “90% safe”? It relies on manual memory management, its simple type system provides limited compile-time guarantees and it lacks a mature c
by neckbeards 11mo ago
How is Zig considered “90% safe”? It relies on manual memory management, its simple type system provides limited compile-time guarantees and it lacks a mature concurrency model (making safe parallelism difficult). Given these gaps, I struggle to see how Zig achieves a consistently high level of safety. It seems no safer than using AddressSanitizer at best?
- scuff3d 11mo agoZig makes a lot of small choices that pile up to push you towards safety/correctness in your code. No hidden allocations is a big one. If something allocates you have to explicitly give it an allocator. This makes tracking allocations much simpler. Combined with the test allocator you can also detect memory leaks. Allocators also simplify memory management patterns. For example, it's trivially ease to use an arena. Then all your little allocations can be freed all at once, eliminating the need to track each individual allocation/free. The defer keyword allows you to place the free call near the allocation (same with other resources that require closing). This isn't always possible of course, but it is a lot of the time and makes it easier to track. Bounds checking is another big one. While loops/for loops with capture groups means you'll basically always see Zig programs iterating through arrays/slices in a manner that ensures you can't go out of bounds. In terms of general correctness you have explicit nullability. If something can be null you have to mark it as such directly in the type. Then when accessing it you have to unwrap it and deal with the null case. Same for errors. If something can return an error it's indicated in the return type. The error has to be unwrapped and then thanks to tagged unions you have to deal with all the different possible cases. There are language conviences like the try keyword that let you side step some of the explicit handling, but even those are easy to track. And you know you're doing something that might bite you. All of that adds up with a dozen other design choices to produce a language that makes it a lot easier to produce correct code. Edit: In terms of concurrency I have to give you that one. The language is still pre 1.0 so we'll have to see what happens.
- neckbeards 11mo agoThanks for the response. I look forward to seeing how the type system evolves. - No hidden allocations: Other languages already provide abstractions for this. Are hidden allocations really a significant issue in modern systems languages, even when managing memory manually? This concern is distinct from memory leaks or general memory safety problems. - Allocators: It's easy to use an arena in C++ or C. - defer keyword: Similar constructs have existed in other languages and even as a GNU C extension for many years. - Bounds checking: Not an issue in Rust. The real source of risk has always been raw pointer manipulation, which Zig doesn't (cannot?) solve. How is it better than address sanitizer? Rust solves this at the language level. - Nullability: GCC has long supported non-null attributes and unused-result annotations and Clang provides _Nullable. C++ goes further with type-level guarantees via std::optional, gsl::not_null and related abstractions. - Errors: Zig’s approach is clean but equivalent mechanisms exist in C++ (std::expected) and Rust (Result and error types). It’s mainly cumbersome in plain C. All of these are useful quality-of-life improvements but they don’t address my problems in systems programming (such as efficient and composable data structures, data movement, scalable concurrency, safe memory management ...and boiler-plate, lots of boiler-plate). These problems are easier to solve with a type system that allows me to make strong correctness guarantees and extend those guarantees. Lastly, I’m unsure that any potential boilerplate reduction would be significant if implemented in Zig.
- scuff3d 11mo agoTo be fair we are pushing up against the limits of my experience here. I'll do the best I can to address your points, but someone more experienced could probably do a better job. I don't think explicit use of allocators is distinct from memory leaks/safety. In Zig you will always know with 100% certainty that something is allocating memory. Making it easier to track what needs to be freed. This isn't always the case with C/C++. Also, as I said before, thanks to the way allocators work, Zig can provide a testing allocator which actively helps detect memory leaks. Their may be tools to help with this in languages like C/C++, but in Zigs case it's build right into the testing framework (which is also provided by the language). To your point about arenas, or other memory management techniques, it's not that they aren't possible in other languages. Obviously they are. But I think it's safe to say that the default memory management mechanism in C/C++ is malloc/free. This leads a lot of people down the path of lots of little allocs that all have to be tracked and paired with a call to free. In Zig, by design, any method that can allocate should take an allocator as an argument (or you pass one when the struct is created and it uses it, but this style is falling out of favor with the community). This pushes developers towards alternative methods of memory management, encouraging them to not just fall back on mallow/free. Arenas, fixed buffers, and other mechanism aren't just possible in Zig, they are first class citizens. I think there is a huge different between "defer" being part of the core language and being part of an extension. By being relegated to an extension it's usage is naturally going to be greatly reduced. The only two other languages I can think of that have this feature are Odin and Go, and Go is garbage collected. Yeah, in Zig you're directly manipulating pointers. That's just how the language is designed. It tries to make it safer, but it doesn't eliminate it, because it's not trying to. If you think this is a bad thing, that's fine. I'm not going to try to change your mind on that point. For nullability, everything you describe is opt in, and tool specific. It is baked into the type system in Zig the same way it is in Rust. It's just handle in (imo) a simpler and more straightforward way. Zig's errors as value approach isn't that much different from Rust's in spirit, and I think both Zig and Rust outclass anything in C++ in this regard. It's an option in C++, but mandatory in Rust and Zig, which I think is where the real power comes from. Ultimately, if you prefer Rust I'm not trying to change your mind. I was just trying to address your question. I don't think any of Zig's individual choices haven't been made in other languages, but I think the way Zig composes them all produces something novel and intriguing. All these little choices push developers in the right direction at every turn. Zig is still young, and it has plenty of rough edges to sand down, but I'm interested to see where it goes. Like I said previously, I think in the long run will see the average safety/correctness of Zig programs be on par with Rust's. I think the two will just fill different niches.