0

This Rust program tries to set each number in a list to the sum of all numbers in the list that are factors of it:

fn main() {
    let mut values = vec![1, 2, 3, 4];

    for va in &mut values {
        let mut cva = *va;
        for vb in &values {
            if (cva % *vb == 0) {
                cva += *vb;
            }
        }
        *va = cva;
    }
    println!("{:?}", values);
}

It does not build:

error[E0502]: cannot borrow `values` as immutable because it is also borrowed as mutable
 --> src/main.rs:6:19
  |
4 |     for va in &mut values {
  |               -----------
  |               |
  |               mutable borrow occurs here
  |               mutable borrow later used here
5 |         let mut cva = *va;
6 |         for vb in &values {
  |                   ^^^^^^^ immutable borrow occurs here

The code works fine if an index-based loop is used over va:

fn main() {
    let mut values = vec![1, 2, 3, 4];

    for iva in 0..values.len() {
        let mut cva = values[iva];
        for vb in &values {
            if (cva % *vb == 0) {
                cva += *vb;
            }
        }
        values[iva] = cva;
    }

    println!("{:?}", values);
}

This is rather awkward, as it requires the programmer to write values[iva] over and over again instead of just va, but with no apparent difference in formality or functionality, just a lexical search-and-replace. Is there a better way to write the first loop to avoid this?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Mark Green
  • 1,310
  • 12
  • 19
  • 1
    One of the [rules of references](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#the-rules-of-references) is that you can never have a mutable reference to an item *and* any other kind of reference to it (*mutable aliasing*). Your original loop attempts to break those rules, as in the first iteration you'd have `va` and `vb` pointing to the same value, one of which is mutable and the other is immutable. Changing `va` would cause the **immutable** value to mutate, which ultimately leads to memory unsafety. – Shepmaster Feb 26 '20 at 02:39
  • Your algorithm also seems suspicious, as evidenced by the output of the compiling version: `[8, 8, 6, 8]`. You test to see if the *sum* is a factor of the item. You also include the item itself, which is always true, so index 0 (`1`) is doubled, then adds `2` and `4`. Then the *next* iteration sees `8`, not the original `1`. Maybe that's what you actually want, but in many cases it's not. – Shepmaster Feb 26 '20 at 02:44
  • *no apparent difference in formality or functionality* — the difference is that the reference-based version says that the value(s) being iterated over cannot change during the iteration, as the references are immutable. The index-based version has no such statement and would be less-optimized. – Shepmaster Feb 26 '20 at 02:45
  • I understand the reasoning, but the immutable borrowing loop is over by the time any change is made to `va`. In the second version of the code, attempting to update `values[2]` inside the immutable loop correctly gives an error; but the first one seems to unreasonably give the same error when it is updated outside that loop. Also, the reference-based version's outer loop is a mutable borrow, so how does it say that the value cannot change during it? – Mark Green Feb 26 '20 at 02:46
  • 1
    *over by the time any change is made* — It's not about when the change happens, it's about when the references co-exist. It's immediate undefined behavior to have both a mutable and immutable to the same value, even if they are never written to. *reference-based version's outer loop is a mutable borrow* — and the inner loop will have a reference to the same value. A mutable reference and an immutable reference co-exist. – Shepmaster Feb 26 '20 at 02:52
  • @Shepmaster That’s what’s getting me though. Values[x] is also a mutable reference that exists at any point after the array is declared. It seems just wrong that a code generator or procedure-like macro could fix the borrow issue by just switching the loop type and search and replacing va for values[x], even though this doesn’t change visible memory behaviour. Or does Rust parallelise for loops by default? – Mark Green Feb 26 '20 at 13:50
  • 1
    *`values[x]` is also a mutable reference that exists at any point after the array is declared* — it is not. When reading from it (`let mut cva = values[iva];`) the value is **copied** out of `values` into `cva`. When writing to it (`values[iva] = cva;`), a mutable reference is created for that expression and no longer. *does Rust parallelise for loops by default* — it does not, but the optimizer might perform loop unrolling. I don't know if that comes into play here or not. – Shepmaster Feb 26 '20 at 13:54
  • Ok. So is there a way to use a range-based for loop that behaves in the same way, so that a mutable reference is only created at the moment of mutation? Rather than the current system which assumes that a "for" which provides a mutable reference is therefore actually mutating those references all the time, even though the act of looping through a list doesn't change itit? I did find a way to avoid some syntactic nastiness by using voluntary extra scopes, but it's still not ideal. – Mark Green Feb 26 '20 at 16:04
  • I assume you mean a reference-based loop, as the index-based loop is the one that uses a range (`..`). No, you cannot, because the two references would coexist at the same time, breaking the rules. It doesn't have to do with the for loop, really, it's a broader concept. Using indices is the correct thing if this algorithm is truly what you need. – Shepmaster Feb 26 '20 at 16:34

0 Answers0