8

This will fail:

fn ppv(arr: &mut Vec<i32>) {
    if arr.len() <= 0 {
       return;
    }
    let mut pp: i32 = 0;
    for &mut val in arr {
        if val == pp {
            pp = val;
        }
    }
    println!("arr is {:?}", &arr);
}

But this will pass:

fn ppv(arr: &mut Vec<i32>) {
    if arr.len() <= 0{
       return;
    }
    let mut pp: i32 = 0;
    for &mut val in arr.into_iter() {
        if val == pp {
            pp = val;
        }
    }
    println!("arr is {:?}", &arr);
}

when I compile the first one, it failed:

error[E0382]: borrow of moved value: `arr`
   --> src/main.rs:12:29
    |
2   | fn ppv(arr: &mut Vec<i32>) {
    |        --- move occurs because `arr` has type `&mut Vec<i32>`, which does not implement the `Copy` trait
...
7   |     for &mut val in arr {
    |                     --- `arr` moved due to this implicit call to `.into_iter()`
...
12  |     println!("arr is {:?}", &arr);
    |                             ^^^^ value borrowed here after move
    |

Why is that? Is it interpreting it differently? First case will implicit call into_iter(), it failed, when I call into_iter(), it passed. What happened?

E_net4
  • 27,810
  • 13
  • 101
  • 139
ban
  • 115
  • 4
  • Related: [Do mutable references have move semantics?](https://stackoverflow.com/questions/62960584/do-mutable-references-have-move-semantics) – Sven Marnach Aug 26 '21 at 10:20
  • This is caused by the difference between `IntoIterator::into_iter(arr)` and `arr.into_iter()`. The `for` loop will implicitly invoke the former, which does not trigger a reborrow. The latter uses Rust's method resolution, which first determines the actual type of the receiver, and it does trigger an implicit reborrow. This difference is rather random, and you just need to live with it. The implicit reborrow rarely causes any problem, and if it doesn't happen, you can always perform an explicit one, so it's not much of a problem in practice. – Sven Marnach Aug 26 '21 at 11:59
  • @user4815162342 [My second sentence is correct.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b76f473fa7ed9cd53eacbf128c624484) – Sven Marnach Aug 26 '21 at 13:27
  • @SvenMarnach I stand corrected - and serves me right for not checking! – user4815162342 Aug 26 '21 at 13:31
  • @user4815162342 Cases are different, since `IntoIterator` is [implemented](https://doc.rust-lang.org/std/vec/struct.Vec.html#impl-IntoIterator) on `&'a mut Vec`, when you call `into_iter(self)`, in here self will be `&'a mut Vec` – Ömer Erden Aug 26 '21 at 13:31
  • The question is always: Is the type of the argument already known to be a mutable reference to the compiler, or is this only determined after performing type inference on the argument? In the case of `Vec::push()`, the type of the first argument is already known to be a mutable reference `&mut Vec`, and type inference only infers the item type of the vector. In the case of `IntoIterator::into_iter()`, the type of the first argument is only known to be a mutable reference after type inference. As I said before, this behaviour isn't particularly intuitive. – Sven Marnach Aug 26 '21 at 13:32

1 Answers1

5

I believe the difference is in reborrow, performed in the latter case, but not the former.

Mutable references are normally not Copy. This is by design, as copying a mutable reference would allow mutable aliasing. But then, the question is how does this work:

fn foo(v: &mut Vec<i32>) {
    v.push(1);  // equivalent to Vec::push(v, 1)
    v.push(2);  // equivalent to Vec::push(v, 2)
}

If the first call to push() receives v, which is not Copy, then the second call to push() should fail to compile with "use of moved value". And yet it compiles, as do the desugared versions.

The reason it compiles is that the compiler automatically performs reborrowing, replacing v with &mut *v. In other words. This transformation is done both on the receiver (self) and the function arguments:

// v is &mut Vec<i32>
v.push(1);        // treated as (&mut *v).push(1)
Vec::push(v, 2);  // treated as Vec::push(&mut *v, 2)

The reborrowed form compiles because it is allowed to create a temporary mutable reference based on an existing mutable reference (e.g. through &mut r.some_field where r is &mut SomeStruct), as long as the temporary reference lives shorter than the original, and you don't use the original reference while the temporary one is live.

You typically notice reborrowing only in the rare cases when it fails. This answer describes such case with serde, where reborrowing failed due to use of generics.

To get back to the original example, your for loop is another example of reborrow failing to happen. Given a mutable reference arr, the difference between for &mut val in arr and for &mut val in arr.into_iter() is that the explicit call to into_iter() is treated as (&mut *arr).into_iter() and thereby allows continued use of arr after the loop. The naked for doesn't do so, and the arr object is lost.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • relevant blog post: https://bluss.github.io/rust/fun/2015/10/11/stuff-the-identity-function-does/ – Svetlin Zarev Aug 26 '21 at 10:11
  • It would be nice if you can explain why the implicit call to `into_iter()` does not reborrow the reference as "normal" calls do. – Svetlin Zarev Aug 26 '21 at 10:14
  • Note that it also works if you do an explicit reborrow: `for &mut val in &mut*arr` ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c451419c79088846c68b0f573ff2df35)). – Jmb Aug 26 '21 at 10:37
  • Nice! But how do I konw when or which condition reborrowing will hapen? – ban Aug 26 '21 at 11:02
  • 1
    @ban There's not much documentation on reborrowing. There is an [open issue](https://github.com/rust-lang/reference/issues/788) about improving the documentation (mainly about the implicit/automatic reborrowing that the compiler is able to do in some cases). Also, [this question](https://stackoverflow.com/questions/66288291/what-is-a-reborrow-and-how-does-it-influence-the-code-the-compiler-generates) has some explanations about explicit reborrows. – Jmb Aug 26 '21 at 11:38
  • @SvetlinZarev I don't know why the reborrow doesn't occur in case of `for`. The way I see it, reobrrow is a best-effort kind of thing, and the effort is presumably missing for `for`. – user4815162342 Aug 26 '21 at 11:53
  • 1
    @ban The question I linked under your question has information about implicit reborrows. – Sven Marnach Aug 26 '21 at 11:55