Not in the sense I meant to make the comparison - Zig doesn't even pretend to do memory safety - if you don't free something manually it won't get freed - this is way worse level of safety than even what C++ with unique_ptr.
In contrast if Rust only ensured that a reference doesn't outlive the scope it's valid in (allocated and cleaned up via RAII), a lot of problems with Rust would go away, like you could have 2 mutable references pointing to the same thing - it'd still be leak free, but you could have data races, and would need some aliasing heuristics like you have in C.
Still most of the complaining of the borrow checker would go away. Imo this is an idea worth exploring in a language, but I'm sure doing this would upset Rust purists.
> Zig doesn't even pretend to do memory safety - if you don't free something manually it won't get freed
Preventing memory leaks isn't normally what people mean by "memory safety".
As an aside: a common memory management approach used in Zig is to have a dedicated memory pool for an operation and simply free all of the memory after the operation is over instead of freeing individual allocations.
> Preventing memory leaks isn't normally what people mean by "memory safety".
People do sometimes expect it to be encompassed by memory safety, even though the Rust documentation clearly states that it does not prevent memory leaks in safe code and offers several obvious ways to deliberately leak memory.
This is simply untrue. Zig offers the same spatial memory safety as Rust does, and that is the kind of memory safety that prevents more dangerous vulnerabilities [1] than the kind Rust offers and Zig doesn't (temporal memory safety).
> if you don't free something manually it won't get freed
Rust also isn't free of memory leaks, and doesn't even pretend to guarantee that all allocations are freed. Lack of memory leaks doesn't fall under what Rust defines as memory safety (which Rust defines as guarantees it does provide, excluding those it doesn't).
You're responding to the emotional appeal part of my post, not the argument itself - I'm sorry for including it, as it had sidetracked the useful conversation.
Zig doesn't have the memory safety mechanisms of C++ level shared_ptr/unique_ptr.
It's essentially C's malloc/free with the added safety of defer (or possibly arenas), but making sure that the program doesn't leak memory (or have use-after-free bugs) falls on the programmer.
And memory leaks are a much lesser problem, as they usually lead to crashes in OoM situation, than gaining access to random blocks of memory.
Besides, the way you trigger memory leaks in Rust/C++ is with circular references, which are much harder to create than just missing a free() somewhere.
If you have memory that's not tied to a function scope's lifetime in Zig (or the scope of an allocator), then you're essentially have to go by C rules
> Zig doesn't have the memory safety mechanisms of C++ level shared_ptr/unique_ptr.
Zig's memory-safety is spatial, which is the more dangerous kind. Your examples are all temporal, and I don't know if I would call them "safety" as the language doesn't enforce or even encourage their use. For example, I work on one of the world's most foundational C++ projects (the HotSpot virtual machine), where neither shared_ptr nor unique_ptr are used at all; the language doesn't complain. There are reference-counted pointer libraries for Zig, too (although I don't think they're popular).
> It's essentially C's malloc/free with the added safety of defer
No, it isn't. First, unlike C or C++, it offers Rust-like spatial memory safety (again, the more important kind). Second, allocation in Zig is much more explicit than in C or C++.
> Besides, the way you trigger memory leaks in Rust/C++ is with circular references, which are much harder to create than just missing a free() somewhere.
They're also much harder to detect, especially when you have a very explicit memory allocation scheme.
> If you have memory that's not tied to a function scope's lifetime in Zig (or the scope of an allocator), then you're essentially have to go by C rules
Again, Zig doesn't seek to offer temporal memory safety, but it does offer the same level of spatial memory safety as Rust does, and while the "rules" of temporal management are similar to C, the way allocation works in Zig makes for a very different experience. An allocating function in Zig looks very different from its C counterpart.
Zig rejects the temporal memory management schemes of C, C++, and Rust - each for different reasons - and prefers a different kind. Of those, only Rust offers temporal memory safety (in the sense that it's controlled by the language) - albeit for a price - and the rest don't.
I don't understand the distinction between spatial and temporal memory safety - from what I gather, spatial memory safety is just preventing out of bounds access of arrays, dereferencing uninitialized or otherwise invalid pointers. But use after free absolutely results in the same kinds of errors (accessing unused memory, or memory used by someone else).
I also don't understand how one can sidestep the issue of ownership of memory - let's say you have a situation where a given piece of memory has changing or ambiguous ownership - like an event sent between two components, that has allocated some dynamic memory.
I'm not saying this just because, I've encountered this exact issue before - many C libraries have event systems where the ownership is not obvious coming from the code - like the sender owns the event, and if you store a reference to it, then you have a dangling pointer if the sender decides to clean it up.
That's why it's important to make it either obvious who owns what, or have some automatic system like GC or unique_ptr or the borrow checker.
This is a very real issue and the only way you can fix it is to follow strong informal conventions the language has no way of enforcing or even communicating to the user.
You mention Zig has a temporal memory management solution of a different kind - may I ask you what it is, because I haven't really seen it.
> But use after free absolutely results in the same kinds of errors (accessing unused memory, or memory used by someone else).
That two bugs result in the same or similar outcome doesn't mean they're as common or as equally exploitable, which is why, in practice, it is distinctly a more dangerous weakness in the lists maintained by MITRE.
> I also don't understand how one can sidestep the issue of ownership of memory
It's not about side-stepping. There are many kinds of bugs in software, and while we'd like to avoid all of them, some problems are, in practice, more problematic. After all, Rust similarly "sidesteps" - as in doesn't prevent - all non-memory-safety-related weaknesses, some of which are more dangerous than dynamic memory safety. If Zig isn't good enough because it doesn't prevent temporal memory safety violations, then by the exact same logic, Rust isn't good enough because it doesn't prevent, say, injection vulnerabilities, that are more dangerous than temporal memory safety vulnerabilities.
> I'm not saying this just because, I've encountered this exact issue before
Sure, it's a real and serious issue. But injection is a more serious issue, which Rust doesn't prevent, and spatial memory violations are also a more serious issue, which Zig prevents just as Rust does.
> That's why it's important to make it either obvious who owns what, or have some automatic system like GC or unique_ptr or the borrow checker.
It is, but it's even more important to prevent code injection attacks, yet Rust doesn't do that. It all comes down to how much it's worth it to pay to prevent certain bugs.
Rust was built because its designers believed that memory safety wasn't worth paying the higher memory footprint or the lower predictability of more popular memory-safe languages, or else there would have been no need for Rust in the first place. Similarly, it's just as reasonable to believe that the price you pay for temporal memory safety in Rust is not worth it.
> You mention Zig has a temporal memory management solution of a different kind - may I ask you what it is, because I haven't really seen it.
It's not a safe solution by any means (as the language doesn't enforce temporal memory safety, just as C++ doesn't), but memory allocation and deallocation in Zig is not the same as in C or C++. In particular, Zig functions that allocate are explicitly marked by having an Allocator parameter (https://zig.guide/standard-library/allocators/). Unlike in C, in Zig you know which functions allocate and using which allocator. That's how Zig is such a great fit for arenas.
I feel like one thing that wasn't made clear - it's not necessarily the job of the programming language to prevent certain issues - SQL injection for example is something that should be handled by the framework (by not allowing to write SQL directly, but using commands with parameters). All the other higher priority issues are like that, it's not within the scope of a language to prevent such issues, just as Rust or Zig or whatever will never prevent phishing attacks.
As for temporal vs spatial memory safety issues, I agree that Zig does maybe help in that a bit (if component A sends an event to B, then A,B and the events having a separate allocator would work) - though I'm not sure if it doesn't introduce its own footguns (how do I know which allocator needs to free which object).
I don't know I haven't used Zig at all, so no idea how all this works out in practice. Memory issues are 100% solved by languages like Java with GC and I wouldn't say they have made software that much less buggy.
> it's not within the scope of a language to prevent such issues
As someone working on adding such a feature to Java, I beg to differ. Python has also recently added a language feature to address injection attacks (https://peps.python.org/pep-0750/).
> Memory issues are 100% solved by languages like Java with GC and I wouldn't say they have made software that much less buggy.
Well, again, the question isn't how many bugs you have, but what the impact of these bugs is. Violations of spatial memory safety are both common and relatively easy to exploit into very dangerous attacks, which is why it's important that Rust and Zig prevent them. But everything is a question of cost and benefit, as in, how much extra effort, or RAM, or CPU it's worth it to avoid certain bugs. The answer depends on the nature of the bug, the nature of the project, and the preferences of developers. Zig and Rust compromise on both correctness and effort, but their compromises are somewhat different. We just don't know if one of these approaches is more likely to be a better sweet spot in more situations (where these languages are used), and if so which.
In contrast if Rust only ensured that a reference doesn't outlive the scope it's valid in (allocated and cleaned up via RAII), a lot of problems with Rust would go away, like you could have 2 mutable references pointing to the same thing - it'd still be leak free, but you could have data races, and would need some aliasing heuristics like you have in C.
Still most of the complaining of the borrow checker would go away. Imo this is an idea worth exploring in a language, but I'm sure doing this would upset Rust purists.