15

This code fails as expected at let c = a; with compile error "use of moved value: a":

fn main() {
    let a: &mut i32 = &mut 0;
    let b = a;
    let c = a;
}

a is moved into b and is no longer available for an assignment to c. So far, so good.

However, if I just annotate b's type and leave everything else alone:

fn main() {
    let a: &mut i32 = &mut 0;
    let b: &mut i32 = a;
    let c = a;
}

the code fails again at let c = a;

But this time with a very different error message: "cannot move out of a because it is borrowed ... borrow of *a occurs here: let b: &mut i32 = a;"

So, if I just annotate b's type: no move of a into b, but instead a "re"-borrow of *a?

What am I missing?

Cheers.

dacker
  • 457
  • 2
  • 9

1 Answers1

9

So, if I just annotate b's type: no move of a into b, but instead a "re"-borrow of *a?

What am I missing?

Absolutely nothing, as in this case these two operations are semantically very similar (and equivalent if a and b belong to the same scope).

  • Either you move the reference a into b, making a a moved value, and no longer available.
  • Either you reborrow *a in b, making a unusable as long as b is in scope.

The second case is less definitive than the first, you can show this by putting the line defining b into a sub-scope.

This example won't compile because a is moved:

fn main() {
    let a: &mut i32 = &mut 0;
    { let b = a; }
    let c = a;
}

But this one will, because once b goes out of scope a is unlocked:

fn main() {
    let a: &mut i32 = &mut 0;
    { let b = &mut *a; }
    let c = a;
}

Now, to the question "Why does annotating the type of b change the behavior ?", my guess would be:

  • When there is no type annotation, the operation is a simple and straightforward move. Nothing is needed to be checked.
  • When there is a type annotation, a conversion may be needed (casting a &mut _ into a &_, or transforming a simple reference into a reference to a trait object). So the compiler opts for a re-borrow of the value, rather than a move.

For example, this code is perflectly valid:

fn main() {
    let a: &mut i32 = &mut 0;
    let b: &i32 = a;
}

and here moving a into b would not make any sense, as they are of different type. Still this code compiles: b simply re-borrows *a, and the value won't be mutably available through a as long as b is in scope.

Community
  • 1
  • 1
Levans
  • 12,752
  • 3
  • 43
  • 50
  • Thx Levans for the answer. I actually checked the subscope variant before to verify that a borrow of `*a` actually took place. Could have been just a false compiler message, after all. Regarding your guess, I would feel very uncomfortable if anytime I use type annotation I would have to be prepared for some weird re-write of the obvious meaning: `let a = b;` should always be just a move or a copy, type annotated or not. – dacker May 29 '15 at 22:32
  • @dacker well, keep in mind that this behavior can *only* be reached with `&mut` references, in which case a re-borrow is simply a copy which respects the borrow rules. – Levans May 30 '15 at 06:52
  • But wasn't that a big point in all the docs I've read so far: &muts are always moved in these cases? NB: the same behavior as above happens with assignments to type annotated &muts as well. – dacker May 30 '15 at 10:53
  • Unless you are playing with nested scopes, re-borrowing or moving a `&mut` reference are effectively the same thing, so it's not surprising docs don't really make the distinction. And what do you call "type annotated &muts" ? – Levans May 30 '15 at 11:00
  • let b: &mut i32; b=a; Where else would I have to expect a non-move (e.g. a copy of some type) if it suits the compiler? Doesn't make me feel confident. But I'll accept your answer if that's the way it is. – dacker May 30 '15 at 11:02
  • The only non-reference types that are not moved are the ones implementing `Copy`, `&` references are `Copy`, and moving or re-borrowing a `&mut` reference are almost the same thing (and I don't see any situation where a move would be preferred over a re-borrow). That's basically all. Still, I'll open an issue in the compiler's Github to suggest they clarify it in the docs. – Levans May 30 '15 at 11:16
  • I see your point. My point is simply that the docs state something different than what's actually happening which, luckily in this case, does no harm. I can't think of an example that depends on a moved value actually being gone, either. Thx for the effort! – dacker May 30 '15 at 11:17