I think it is important to note that in 59nadir's example, the reason Rust gives an error and Odin doesn't is not memory safety. Rust uses move semantics by default in a loop while Odin appears to use copy semantics by default. I don't really know Odin, but it seems like it is a language that doesn't have RAII. In which case, copy semantics are fine for Odin, but in Rust they could result in a lot of extra allocations if your vector was holding RAII heap allocating objects. Obviously that means you would need to be careful about how to use pointers in Odin, but the choice of moving or copying by default for a loop has nothing to do with this. For reference:
Odin (from documentation):
for x in some_array { // copy semantics
for &x in some_array { // reference semantics
// no move semantics? (could be wrong on this)
Rust:
for x in vec.iter_ref().copied() { // bytewise copy semantics (only for POD types)
for x in vec.iter_ref().cloned() { // RAII copy semantics
for x in &vec { // reference semantics
for x in vec { // move semantics
C++:
for (auto x : vec) { // copy semantics
for (auto &x : vec) { // reference semantics
for (auto &&x : vec) { // move semantics
And why do you think that bug is relevant in the case of a loop that prints the elements of a container? We can all see and verify at a glance that the code is valid, it's just not provably valid by the Rust compiler.
I feel like these posts trying to show possible memory issues with re-allocated dynamic arrays are missing the point: There is no code changing the underlying array, there is no risk of any kind of use-after-free error. This is exactly the kind of case where all of this jumping through hoops shouldn't be needed.
Ok, so we've established that the loop can be verified as not changing the container in any way, what makes you believe this shouldn't be obvious to the Rust compiler?
When code that modifies the container is added, it should be understood and then correctly errored about, I don't get why this is such a crazy concept to people.
The point here is that you pay the cost for an error that can't happen. It's just a micro example of a much more general issue that boils down to:
The Rust compiler does a lot to find and mitigate bugs, it's amazing, but it also rejects completely valid programs and solutions because it simply isn't good enough (and it's a difficult enough problem where I'm prepared to say it will never be good enough). You can either pay that cost constantly and for certain problems be dealing with it (a lot) for no gain whatsoever (because the bugs it was trying to prevent weren't actual issues or are in fact imperatives because the thing you're doing requires them) or you can choose not to.
I don't think it's particularly useful to make excuses for the compiler not understanding very basic things in simple examples and indirectly argue that it would be too complicated to see what the loop is doing and act accordingly. Rust already signed up for a very complicated compiler that does all kinds of crazy things in order to mitigate bugs; this type of introspection would increase the accuracy of it a lot.
> You can either pay that cost constantly and for certain problems be dealing with it (a lot) for no gain whatsoever (because the bugs it was trying to prevent weren't actual issues or are in fact imperatives because the thing you're doing requires them) or you can choose not to.
Alternatively, you can use Rust so much these limitations become second nature, and thus don't make them in the first place.
> I don't think it's particularly useful to make excuses for the compiler not understanding very basic things in simple examples and indirectly argue that it would be too complicated to see what the loop is doing and act accordingly.
Great idea, until it stops working. It runs into the paraphrased quote: "Any sufficiently complicated borrow checker is indistinguishable from Dark Magic".
First you say, well, the compiler should be sufficiently smart to figure out case A1 should work, then you add that, but then arises another case A2 that the compiler is sufficiently smart to figure out and so on.
However, you add a bunch of these "sufficiently smart" borrow rules, and you'll end up with a mess. A1 and A2 don't work if A432 is applied, but do work if A49324 is given if the A4 and A2 are satisfied.
The harder the borrow checker is to understand, the more difficult it is to construct a mental model that's useful.
In summary: while I'm not against improving the borrow checker, but the problem is that it needs to be balanced with the opportunity cost of understanding how it approximately works.
> Ok, so we've established that the loop can be verified as not changing the container in any way, what makes you believe this shouldn't be obvious to the Rust compiler?
I would be quite happy for the Rust compiler to be able to perform more powerful analysis and make writing code easier. What I object to, and I think that quite small Zig code snippet highlights, is that dealing with those shortcomings
I make custom 3D engines and I can tell you that it would not be a net benefit for us to use Rust. That's why I added "for certain problems" as a qualifier; there are use cases where Rust would be a net negative.
There are also plenty of use cases where Rust is actually useful and provides guarantees about things that you want guarantees about.