> The borrow checker doesn't stop most memory errors, just the easy ones at the cost of making it painful to write basic data structures
I'm not the OP but I think the point here is that the borrow checker is only applicable for the "easy" code (which does make up an awful lot of code), but even "basic data structures" very often require abandoning the borrow checker. In such a case, it's perfectly accurate to say "the borrow checker doesn't stop most memory errors" since you must turn it off when you write the code responsible for "most memory errors" -- at least that is the case in my experience.
So in this type of discussion, consider arguments about the capabilities of "the borrow checker" as analogous to arguments about "the memory safety of Rust in general", rather than taking it so literally.
If you want to make a sensible argument on this front, it should be about unsafe belonging mostly in crates that have been thoroughly looked over and battle-tested by the community. That is after all, a major reason why the number of such errors is still impressively small. But unsafe errors are still Rust errors, and while the borrow checker is wonderful, it and Rust should still be held accountable for problems in necessary code that requires disabling the borrow checker.
I think Zig does pretty well on this front, with there being pros and cons to not having a clearly demarcated "unsafe" boundary built into the language. Lacking an unsafe keyword does make it more unsafe, but in my experience, not very much.
I maintain Rust cryptography libraries with over 30-40k LoC, and I have never resorted to unsafe to bypass borrowck. Our code is as performant as any C impl, without the memory safety bugs.
I wouldn't expect cryptography to need much unsafe. I've never worked in the area, but I expect code mostly falls into 3 domains: parsing, "business" logic (negotiating algorithms, checking certificates/signatures the right way, etc.), and actual crypto algorithms. I see great benefit for Rust in the first two domains. In the algo domain, I don't see how memory safety adds anything as memory issues are an insignificant problem. And there are big challenges like sometimes needing constant-time impls, etc., and how to do that without always having to dip down to hand-written assembly for each target platform. No language I know of provides help in getting bitslicing right, for example.
Again, Rust is great for an awful lot of code, but we should still assess Rust's memory safety claims against the data structures code where you often need to disable the borrow checker. An awful lot of people only use safe wrappers around well-tested unsafe code, which is great, but the unsafe code is still being executed and may still have memory "unsafe"ty.
> I don't see how memory safety adds anything [to algos in cryptography] as memory issues are an insignificant problem.
I'm not terribly experienced in cryptography either, but I'm pretty sure that memory safety is a HUGE deal in cryptography as a whole. Heartbleed[0] was a memory safety issue. Python's cryptography package switched to Rust specifically because of memory safety [1].
Heartbleed was in the parsing/business logic areas -- the areas where I said Rust's memory safety provides great benefits. When I say crypto algos, I mean implementing S-boxes in 3DES, etc.
If you agree that Rust's memory safety is hugely beneficial in cryptography, then I'd love to hear other (sub)domains (e.g., kernel code, networking code, blockchain, ...etc) that necessitate tricky data structures, making Zig a better fit than Rust (which, IIUC, is your main point here).
I don’t have a “zig/rust is better than rust/zig” point here. The point was to say you have to judge Rust’s success against the unsafe code it occasionally forces you to write (you don’t get to hand wave it away), especially since a great example of such unsafe code is data structures code, and that is where a lot of my memory errors occur. Then we got diverted into some minor points about how much benefit does memory safety at compile time give you in different domains.
I wouldn’t build a company or long-term project around Zig right now, since it’s quite young, so if the choice was just those two, I’d probably always pick Rust. But ignoring that, since Zig will probably “graduate” in a year or two: both are good for all those domains, and I’d be hard-pressed to say one is better or worse than the other without further constraints. Zig certainly seems less indirect and more lightweight, and has less of an immediate cognitive burden, but Rust’s additional burden there is providing some additional value. I really don’t like Rust’s allocation story though — AFAIK there are plenty of allocations in std that can only be controlled by swapping out the global allocator, possibly even at link time. C++ is clunky on this front too, just in a different way. C is “better” simply because the standard library is so meager, but that has never been a problem for me in practice.
> You have to judge Rust’s success against the unsafe code it occasionally forces you to write (you don’t get to hand wave it away).
Agreed.
> I really don’t like Rust’s allocation story though.
I presume this is because of the lack of ergonomic handling of OOM? Only time I've run into OOM was when training deep learning models (in Python backed by C++) on a GPU, but if I were to do that in Rust, I'm pretty sure I'd be able to avoid the memory leaks that caused OOM since allocations in a deep learning training loop are almost always deterministic.
So I would love to learn about the domains where you think OOM is a huge issue (apart from kernel code, which I'm already familiar with).
Btw, thanks for all the replies! I'm learning with every one.
It's not at all about OOM, actually. It's about performance, and to a lesser degree, simplifying code or architecture. Zig's FixedBufferAllocator and ArenaAllocator are very simple and very useful, and they conform to the Allocator interface that's used for every bit of allocation in std and non-std. Anyone who has written a lot of C or a certain kind of C++ will be very familiar with the concepts, and will have used them a lot. They're nothing revolutionary, but not having a "default" global allocator and having a culture of passing the Allocator as a parameter means there's a nice way to actually use these things without always having to rewrite/modify someone else's code -- all Zig code I've seen passes the Allocator as an argument. This also means you have some idea of when allocations occur, which is another thing I often want to know (I used to work on ultra low latency systems, measuring in nanoseconds -- no allocations allowed in the hot path).
> The borrow checker doesn't stop most memory errors, just the easy ones at the cost of making it painful to write basic data structures
I'm not the OP but I think the point here is that the borrow checker is only applicable for the "easy" code (which does make up an awful lot of code), but even "basic data structures" very often require abandoning the borrow checker. In such a case, it's perfectly accurate to say "the borrow checker doesn't stop most memory errors" since you must turn it off when you write the code responsible for "most memory errors" -- at least that is the case in my experience.
So in this type of discussion, consider arguments about the capabilities of "the borrow checker" as analogous to arguments about "the memory safety of Rust in general", rather than taking it so literally.
If you want to make a sensible argument on this front, it should be about unsafe belonging mostly in crates that have been thoroughly looked over and battle-tested by the community. That is after all, a major reason why the number of such errors is still impressively small. But unsafe errors are still Rust errors, and while the borrow checker is wonderful, it and Rust should still be held accountable for problems in necessary code that requires disabling the borrow checker.
I think Zig does pretty well on this front, with there being pros and cons to not having a clearly demarcated "unsafe" boundary built into the language. Lacking an unsafe keyword does make it more unsafe, but in my experience, not very much.