My experience is that Rust's approach is definitely better in terms of correctness than using valgrind during testing.
My intuition is that the advantages Zig brings to the table will not tip the balance.
That being said, the choice Zig makes is absolutely the right one. Rust fills the niche of a better and more correct C++ without fixing the issues of slow compilation and language complexity.
Zig fixes so much of what's wrong with C without abandoning the advantages of language simplicity and locality of reasoning. I love Zig, but need a medium sized non-work project for it.
https://github.com/ziglang/zig/pull/5998
edit: For more details, see
For memory related issues I find it sufficient.
One aspect where it is probably not as good as Rust is for threading related issues, as it relies on inserting runtime checks which may or may not trigger depending on the number of threads attempting access.
It also works with iterators-generally by sticking some extra data in the iterator in dev builds, so it can check for out of bounds access.
If I do have some code that uses C pointers + size, I'll insert some dev build assertions.
We see similar tradeoffs of soundness in formal verification as well. We're not talking about exactly the same thing here (because affine type safety is compositional) but the general principle is the same: soundness has a cost, and it is not necessarily the most efficient way of achieving a required level of correctness.
Anyway, I think that both Rust and Zig have very interesting approaches to safety, but I don't think we know enough to claim one is more effective than the other at this time.
RAII is not precluded by explicitness, you could require all values that require cleanup to be syntactically marked in some way and it would still be RAII. defer also cannot handle resources whose lifetimes do not correspond to nested scopes (eg. the elements of an ArrayList) like RAII or a GC can.
I think this one is TBD.
> defer also cannot handle resources whose lifetimes do not correspond to nested scopes (eg. the elements of an ArrayList) like RAII or a GC can.
Yep, defer won't work if there is no known lifetime scope, but I think this one is actually a good tradeoff to make in a low-level language. Don't get me wrong -- I love tracing GCs and think that they're the right choice for the vast majority of application software, plus there have been great strides made in GC capabilities in the past few years, but in the domains where low-level languages are appropriate there is a different set of constraints. Low-level programming is not like high-level programming, and IMO it's wrong to even try to make them look alike.
It can, it's just more explicit about it. In a language with destructors, you'd do RAII here by having a list destructor that cleans up each element in turn. In a language with defer, the same destructor becomes a regular function that you'd invoke in the deferred expression.
The other issue is since there is no generic notion of a destructor, it isn't possible to write generic functions that destroy elements. If you call, say, replace_range on a list of strings, it will leak the replaced strings.
That's separate from the destructor for the entire list. It does mean that the code that removes an element from the list has to explicitly invoke the destructor for it - which is in agreement with using "defer" to explicitly invoking destructors for locals.
> The other issue is since there is no generic notion of a destructor, it isn't possible to write generic functions that destroy elements.
But you can have a generic notion of a destructor - that's orthogonal to whether destructors are invoked explicitly. You just have an interface (or trait, or whatever it's called) that exposes a destructor method for a type.
This sounds like "we can't guarantee the most important thing, so it's unclear whether it's useful to guarantee this other thing," but that's a bizarre statement, so am I misinterpreting?