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|4 months ago
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|4 months ago
- 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.