Comment by kmeisthax
2 days ago
I was not aware of the antagonism from the Zig / Fil-C people to Rust, but it makes absolutely no sense.
Like, of course you can prevent all memory safety errors by using a garbage collector. That has been known since the 90s. In fact, there was a good two decades after Java released where all the research on memory safety just stopped, because the standard answer became "use a GC". If you couldn't use GC, you were stuck with languages made prior to Java - which in practice meant just C - or the one language everyone tried to staple every new programming paradigm onto, C++.
In fact, part of why C++ became such an untameable beast of a language is because it became load-bearing for non-GC projects. Anything not in the ISO C++ standard was, in practice, something you just couldn't do in native code. Oh, of course you can't introspect structs in a compiled language, of course macros are useless and unhygenic, of course templates have to be monomorphized and bloat your binary size. And so the pressure for C++ to be an all-singing, all-dancing, all-dressed language grew.
Rust's big story is memory safety, but the more Rust I wrote, the more I realized that the memory safety is only part of the picture. Rust has a very nicely curated selection of features that allows the language to remain understandable despite the sophistication of the compiler. Memory safety is a selling point, sure, but it is also the lubricant that makes working with those features pleasant.
If you want a real complaint about Rust, it's that some features are oversimplified in ways that make certain scenarios harder and make some features way more "magic" than they should be. Have you ever tried writing Futures code without making use of the async keyword? It's nearly impossible, for several reasons; the main being that Rust's type systems cannot express self-referential borrows. This also makes returning a reference to something in an Rc or RefCell you own more difficult[0]. And there are numerous other states memory can be in that are hidden from Rust's type system. Rust can't even represent a real destructor fn. Dropping a value multiple times, or using it after it's been dropped, is explicitly forbidden; but Drop impls still can't take values out of themselves because Rust doesn't have an "owned reference" - i.e. memory you can take values from but can't deallocate.
But none of this compromises memory safety - it just makes certain things harder than they should be.
[0] Strictly speaking, there's an owning_ref crate that manages this; stdlib is also working on a "mapped mutex guard" type that would do the same thing without a dependency.
> I was not aware of the antagonism from the Zig .. people to Rust, but it makes absolutely no sense.
Lol, have you ever watch Andrew Kelley speak? I've only seen 3 or 4 talks he's given, but he frequently makes not-so-subtle digs towards Rust. Zig's home page prominently displays "Focus on debugging your application rather than debugging your programming language knowledge" which sounds horrible to me, but is clearly an anti-rust statement. Competition is good, and Rust and Zig cater to different people, but when the primary personality behind Zig is leads the way, it shouldn't be a surprise that some tribalism-influenced followers lean hard into it.
> "Focus on debugging your application rather than debugging your programming language knowledge"
This sounds like it's made by someone who fears discovering they don't know something more than discovering they made a mistake.
I am the reverse: I would prefer to debug my programming language knowledge over debugging my application. I hate debugging applications specifically because making a mistake is SO much worse to me than discovering there is a fact I didn't know yet.
And, of course, debugging your application is discovering there are unknown unknowns in deployment, the worst of the quadrant of knowns and unknowns.
Debugging PL knowledge: When you do 4+4 on a Tuesday at an even numbered address it's 9 - is all your software safe against this?
“Focus on debugging your application rather than debugging your programming language knowledge” to me sounds like it is directed primarily at C and C++., it’s a much more direct translation.
It could also apply to Rust though. It's on the complexity scale of C++, not C.
1 reply →
The thing that has changed is that there are spaces where security is being taken more seriously, and C's memory unsafety became a deal breaker there. I do think that providing a mechanism to run existing C software that isn't performance sensitive in a way that mitigates its limitations is very worthwhile.
I think the "static analysis" and the "runtime checks" approaches are complementary, not in opposition, making any noise around having to choose one or the other moot.
One would think that by now it would also be on WG14 roadmap for language evolution.
Same in regards to WG21, while more active in improving safety, there are plenty of politics between the profiles approach faction and everyone else.
> Like, of course you can prevent all memory safety errors by using a garbage collector. That has been known since the 90s.
No, it's been known since GC was invented.
> In fact, there was a good two decades after Java released where all the research on memory safety just stopped, because the standard answer became "use a GC".
Having done all of my research on memory safety after Java was released, I find this statement a little exaggerated... (e.g., https://barnowl.org/research/pubs/98-pldi-regions.pdf, https://barnowl.org/research/pubs/07-hotos-linux.pdf)
Yeah, my understanding (which is not from first-hand experience) is that a huge part of the origin of Rust was that there was a lot of research in memory safety techniques that had been going mostly unused by mainstream languages, so the idea was "Why not try to make a language using some of it?"
AT&T was already doing that as language to replace C, Cyclone.
AT&T even had a full OS, where C only had minor role, the microkernel and Dis VM/JIT, with the whole userpace implemented in Limbo, designed by the creators of UNIX and C.
4 replies →
*nod* They're from before I started bookmarking rigorously, but in the early days of Rust, there was a sentiment in the blog posts from the people developing it that the point of Rust was "to give good ideas a second chance" and that Rust was intentionally boring and un-innovative.
> you can prevent all memory safety errors by using a garbage collector
Garbage collection and a higher-level language that controls allocations handles 2/3 of the memory-safety problem space (using memory you shouldn't via use-after-free, or using memory you shouldn't before allocation/via arbitrary address access), but the other 1/3 isn't addressed by GC: out-of-bounds access on properly-allocated structures.
GC-or-not doesn't stop a pointerful language from addressing memory off the end of an array, and the best techniques we have to deal with that today are imperfect (but still pretty good!) compiler inference for BCE, memory protection to limit the scope of possible overreach, or slowing down runtime by inserting checks.
The rest of your points are well-taken; just pointing out that GC isn't the solution to all causes of memory-unsafety, just 2/3 of 'em.
You forgot the other three thirds GC doesn't handle: in-bounds writes to memory currently in use by another processor. One of the basic ideas behind Rust's memory safety story is that eliminating race conditions necessarily requires a good memory safety story, especially regarding temporal memory safety.
...of course, the managed code languages had an answer to this: put a lock on every managed object you allocate.
But they didn't actually make you acquire the lock - it only comes into play if you declare a method to be synchronized or if you acquire the lock externally. Which is a problem because most of these languages ALSO are designed to act as a security boundary for untrusted code - something Rust doesn't even offer! Which means even though the language doesn't enforce race-freedom, it has to enforce enough of it anyway to keep racy code from corrupting the language heap (which is now security-sensitive). No other language (not even Rust) does this - if you write a race in unsafe Rust, it is actually UB, but in Java you actually still have some guarantees about what your program will do.
Actually, let me throw a bone to the Zig people: Unsafe Rust actually imposes more UB than C does, and it is more difficult to write sound unsafe Rust. If you're writing FFI code[0], you're probably fine. But if you're writing new memory abstractions, you're probably going to run into Rust's requirement that all mutable references be exclusive. If this ever fails, your program is already in UB and all bets are off. Morally speaking, Rust sprinkles the equivalent of C's restrict keyword over every &mut in your program. In fact, this UB is so strict that the Rustc people have had to turn this on and off multiple times in the past because LLVM would miscompile sound / safe Rust code if it was told about the aliasing restriction.
In practice, however, this doesn't really matter. Rust has all sorts of soundness holes nobody would ever trigger by accident. There's a long standing trait-handling bug that lets you confuse memory types in safe Rust; and any OS that exposes process memory as a writable file lets you do the same thing. In Java, at least the former would be a CVSS 10.0 security vulnerability, but it doesn't matter in Rust, because safe Rust is not a security boundary. It's a set of tools to keep you from shooting yourself in the foot. And, in practice, Rust does a pretty good job of that.
[0] Or more generally, unsafe code where you have two unsafe pieces that you have to guarantee are used together. For example, if you have a C-style callback API that takes a function pointer and a data pointer, and then calls the function with the data, you have to use Box::into_raw() and Box::from_raw() to maintain ownership over the data. from_raw is unsafe, but all the obvious ways of using it are sound.
> One of the basic ideas behind Rust's memory safety story is that eliminating race conditions
Rust does not prevent race conditions. You're getting confused with data races.
However GC's solution to data races (make every load/store act as very relaxed atomic instructions) makes race conditions much easier to write.
4 replies →
Fearless concurrency only works as advertised when those data structures aren't exposed via the MMU to other processes, or are handles to resources like files or database connections, where each thread can do whatever they want without type system checks.
All relevant scenarios when the goal is systems programming.
> Like, of course you can prevent all memory safety errors by using a garbage collector. That has been known since the 90s.
Since the 60s actually, starting with Lisp, Scheme, CLU, Cedar, Smalltalk, Cedar,...
> Strictly speaking, there's an owning_ref crate
owning_ref is unsound and shouldn't be used. self_cell/yoke/ouroboros are much better alternatives, ableit more complex
It is not universal. But imagine you enjoying a programming language (zig, odin, c, c++, …) and half the time some post/article about it comes up, 15% or so of the comments are “kindly” missionaries who just want to let you know how backwards you are, but that there is hope for you yet - as if anyone has been living under a rock solid enough to not hear about Rust.
Honestly, it gets tiresome and adds nothing useful to all these posts. And since those drive-by missionaries aren’t spreading the good word of ocaml/java/go/haskell but Rust - well, then eventually you tire of the Rust community. It has a missionary/redemption problem.