Reusing a dead index is bad, but not as bad as full memory unsafety. It can cause application errors for sure but the integrity of the runtime is preserved.
Segregated, typed, heaps (never reuse the memory for a different type after a free) could allow similar safety.
I would say that this is an extremely narrow view of "memory safety", and totally ignores the broader concept of "lifetime safety". Yes, if the thing you want to prevent is crashes due to segfaults, indexes in Rust prevent that. But I would argue there's no practical difference between "crash due to segfault" and "panic due to index overflow" (which is definitely possible with the Rust model): both are just different ways of looking at "you dereferenced an invalid pointer". It does prevent the pointer writing into different data structures though, which is definitely an advantage of Rust (which comes with the runtime cost of bounds-checking, but that is probably worth it).
But more generally: imagine you're making a game where you shoot a bunch of dudes, so you have a `std::Vec<Dude>` to store them, and elsewhere in the code, you reference this vec using indexes. When you shoot a dude, you want to remove it from this array, but being a programmer concerned about performance, you don't want to literally delete the entry, as that is an O(n) operation. Instead, you mark the dude as "despawned" or whatever, and then when you want to spawn a new dude, you keep a list of despawned dudes and just recreate the new dude in the same place in the array. If other places in the code are holding on to indexes to this array, the index is now invalid, but it will not be detectable: it'll just point to the wrong data, which can wreak all sorts of havoc.
This is almost exactly equivalent to "use-after-free" issues in C. In most cases, `free()` will not literally free the memory, it will mark the memory as unused and stored in a free-list, to be available for the next `malloc()` call. If you're holding on to a pointer that has been free()d, it's not that the program will segfault, it's much more insidious than that: it will point to memory that is apparently valid, but actually garbage.
This is precisely the kind of lifetime issue the Rust borrow checker was designed to solve, but by using indexes like this, you've basically just turned the borrow checker off without realizing it. If the borrow checker is intrusive enough that this becomes a very common pattern in Rust, I personally think it does serve as a pretty good counter-argument to a lot of things people say about Rust. Rust people are very quick to sing the borrow checker's praises and say that "as long as you learn to work with it, it stops becoming a problem!". If it stops becoming a problem only because you sneakily turn it off using tricks like this, maybe it's a bigger problem than people are willing to admit.
> But I would argue there's no practical difference between "crash due to segfault" and "panic due to index overflow
One leads to undefined behavior and is potentially exploitable in a way that allows for full control over your program's execution. That's the difference.
No one is arguing that the bug you've described isn't expressible in Rust - it obviously is. It's just not what the borrow checker is for because the borrow checker is for memory safety issues, and you're describing a logical bug.
From a security perspective, yes Rust is better, but it's still a security issue: if you can make a Rust program panic due to overflow, that's a DoS attack.
I think this description is a very narrow way to look at memory safety (and again: totally ignoring the broader issue of lifetime safety), if I'm going to be honest. In my Rust program, I have two functions `spawnDude()` returning an index and a `despawnDude()` taking an index. In C, i have `malloc()` returning a pointer and `free()` taking a pointer. The lifetime issues are the same: just like I shouldn't `free()` a pointer twice, i shouldn't `despawnDude()` twice, and I shouldn't use a dude after I've despawned him. The implementation could even be very similar: using arenas (which is essentially what the Rust array is) and free-lists.
Again: these were the issues Rust was designed to solve, and the borrow checker is the tool it uses to solve them. And it absolutely does do that, if you use the native Rust constructs: this is the true super-power of Rust. The reason why it's so much easier to work with indexes is because you've deliberately chosen not to have the borrow checker analyze this situation. If that is something you have to do a lot of (and I've seen it a number of times, not just in the project the parent mentioned), it does say something important about how the borrow checker limits the expressive power of the language, if you have to turn it off in this way.
I fail to understand the notion between "memory safety bugs" and "logical bugs"? E.g. logical bug is what can lead to memory safety bugs so I don't quite understand the point being made.
Panics are implemented in the same way as exceptions are in C++, beyond that Rust doesn't have a concept of exceptions at all. Fallibility is expressed in terms of sum types that signal state for success or failure with Result.
> Panics are implemented in the same way as exceptions are in C++
If this is true or at least fundamentally very close to each other, and given that exceptions can be abused for arbitrary code execution [1] [2], then it follows that Rust is no different/safer with respect to that, no?
> But I would argue there's no practical difference between "crash due to segfault" and "panic due to index overflow"
Nope. If you're lucky, you get a segfault.
> imagine you're making a game
Actually, I would love to be making games. The benefits Rust provides aren't huge benefits in Game Dev, we get it. In fact, isn't this whole thing just a rehash of Jonathan Blow's ECS critique? But most of us aren't making games.
Imagine instead you're a not making a game, and you see CVE after CVE because of memory safety. Customer data gets stolen, things like SSNs. People's lives are impacted. I suppose a game crash is annoying, but way less annoying than someone's/my personal info being leaked.
Sure, we could go on as usual, blame "human error" and say everybody needs to do better. Or, we could accept better over perfect, and try to mitigate common errors that seem to happen often. You can still use `unsafe`. I guess by this argument, seat belts are useless, too, because they don't stop traffic fatalities completely. And so are all aviation safety improvements.
We're still here having the "Rust offers no/marginal benefits (to game dev and other niches)". Ok.
I want to be clear: I think Rust offers ENORMOUS security benefits over C. No one is disputing that. I personally love Rust, I think it's a great language. This is not an attack on the language, this is an observation that if you use this particular pattern (indexes instead of pointers), you are doing something dangerous, and you can no longer rely on the borrow checker to validate lifetimes.
It seems to me to be a pretty unarguable point. It's frankly a bit shocking to me to see this vehement of a disagreement.
I think there are security vulnerabilities I’ve read about Mach that stem from tricking the kernel into accessing an object as the wrong type. This kind of type confusion would be possible if you had a heterogenous container and had stale index references.
This is a really good analysis, and it points to a central flaw in Rust. As a practical matter, you have to have more than one pointer to a data structure all the time. You really can't implement some things without it.
Rust makes this really, really difficult. If you can live with immutable pointers, you can have as many as you like, but the lifetime syntax you have to juggle can become a horror show, and can force lifetime annotations on a whole chain of structures. If you can't live with immutable references, then you are forced to use the unnecessarily ugly Rc<> syntax.
I often find myself using the array indexes trick, which leads to buggy code.
In Java, I never had to do this. Multiple "pointers" are fine. The garbage collector takes care of memory safety. In this one sense, Rust is a less-safe language than Java.
I have been writing Rust for two years now. I would not use it again if I could avoid it. It is a giant waste of time.
Segregated, typed, heaps (never reuse the memory for a different type after a free) could allow similar safety.