Comment by kmeisthax
2 days ago
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.
Rust does prevent race conditions.
Is this the classic "split out obviously wrong facts to get corrected"?
I'll take the bait then. Here's rust official documentation stating that it doesn't prevent race conditions: https://doc.rust-lang.org/nomicon/races.html
Rust helps prevent some race conditions, but an unqualified "prevents race conditions" sounds like it eliminates them, hence why GP says it does not.
It prevents "user A kicked B" and "user B revoked moderator permission from A" from arriving in a queue near the same moment?
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.