23

I was under the impression that mutable references (i.e. &mut T) are always moved. That makes perfect sense, since they allow exclusive mutable access. In the following piece of code I assign a mutable reference to another mutable reference and the original is moved. As a result I cannot use the original any more:

let mut value = 900;
let r_original = &mut value;
let r_new = r_original;
*r_original; // error: use of moved value *r_original

If I have a function like this:

fn make_move(_: &mut i32) {
}

and modify my original example to look like this:

let mut value = 900;
let r_original = &mut value;
make_move(r_original);
*r_original; // no complain

I would expect that the mutable reference r_original is moved when I call the function make_move with it. However that does not happen. I am still able to use the reference after the call.

If I use a generic function make_move_gen:

fn make_move_gen<T>(_: T) {
}

and call it like this:

let mut value = 900;
let r_original = &mut value;
make_move_gen(r_original);
*r_original; // error: use of moved value *r_original

The reference is moved again and therefore the program behaves as I would expect. Why is the reference not moved when calling the function make_move?

Code example

jtepe
  • 3,242
  • 2
  • 24
  • 31
  • 2
    Explicitly instantiating (`make_move::<&mut i32>(r_original);`) works like the original function (no move). Fascinating; I'd assume borrow checking is happening before type inference. – Veedrac Aug 22 '15 at 09:37
  • From dacker's answer I assume that is the case: Explicitly annotating the type to be a mutable reference triggers a re-borrow of the contents instead of a move, leaving the original reference to be usable again once the new reference (here in make_move's scope) goes out of scope. – jtepe Aug 22 '15 at 11:10

4 Answers4

15

There might actually be a good reason for this.

&mut T isn't actually a type: all borrows are parametrized by some (potentially inexpressible) lifetime.

When one writes

fn move_try(val: &mut ()) {
    { let new = val; }
    *val
}

fn main() {
    move_try(&mut ());
}

the type inference engine infers typeof new == typeof val, so they share the original lifetime. This means the borrow from new does not end until the borrow from val does.

This means it's equivalent to

fn move_try<'a>(val: &'a mut ()) {
    { let new: &'a mut _ = val; }
    *val
}

fn main() {
    move_try(&mut ());
}

However, when you write

fn move_try(val: &mut ()) {
    { let new: &mut _ = val; }
    *val
}

fn main() {
    move_try(&mut ());
}

a cast happens - the same kind of thing that lets you cast away pointer mutability. This means that the lifetime is some (seemingly unspecifiable) 'b < 'a. This involves a cast, and thus a reborrow, and so the reborrow is able to fall out of scope.

An always-reborrow rule would probably be nicer, but explicit declaration isn't too problematic.

Veedrac
  • 58,273
  • 15
  • 112
  • 169
  • I never thought of it that way. However it seems logical. By "cast away pointer mutability" you mean getting a shared reference from an mutable reference? Like let r: &i32 = r_mut; where r_mut is &mut i32. – jtepe Aug 22 '15 at 18:07
  • Nice explanation. This really makes sense. My guess regarding `'b` would be: it's the intersection of `'a` which encompasses `move_try`'s body and the nested scope where `new` is defined; so `'b` would be this nested scope at the end of which `new`'s borrow ends. – dacker Aug 22 '15 at 22:41
  • As per the explaination, first 2 snippets seem equivalent but they show different error messages [Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7863000c51a6497ffa2a26947cabb138). First snippet says value moved whereas second snippet says still borrowed. Did I misunderstood something? – Mihir Luthra Dec 02 '20 at 16:08
  • 1
    @Mihir Technically `let new: &'a mut _ = val;` is a reborrow, it's just a no-op reborrow since the lifetimes are identical. – Veedrac Dec 03 '20 at 01:53
  • @Veedrac, it makes sense when you say "&mut T isn't actually a type". Although, I am curious, `let new = val` moves the mutable reference. Then if we had to explicitly annotate `new` wanting data to be moved from `val` to `new`, how would be do that? `let new: _ = val`. What would be the exact type of `_`? – Mihir Luthra Dec 03 '20 at 06:21
  • 1
    @Mihir I don't think you can, unless you have the whole type in a type variable. – Veedrac Dec 05 '20 at 12:08
5

I asked something along those lines here.

It seems that in some (many?) cases, instead of a move, a re-borrow takes place. Memory safety is not violated, only the "moved" value is still around. I could not find any docs on that behavior either.

@Levans opened a github issue here, although I'm not entirely convinced this is just a doc issue: dependably moving out of a &mut reference seems central to Rust's approach of ownership.

Community
  • 1
  • 1
dacker
  • 457
  • 2
  • 10
3

It's implicit reborrow. It's a topic not well documented.

This question has already been answered pretty well:

  1. how implicit reborrow works
  2. how reborrow works along with borrow split
Izana
  • 2,537
  • 27
  • 33
1

If I tweak the generic one a bit, it would not complain either

fn make_move_gen<T>(_: &mut T) {
}

or

let _ = *r_original; 
kmdreko
  • 42,554
  • 6
  • 57
  • 106
ggaowp
  • 96
  • 1
  • 4
  • This doesn't really help answer the *"why"* behind this behavior. And the second example is just dereferencing and not really relevant here. – kmdreko Dec 19 '21 at 08:04
  • @kmdreko by second example, I meant if I changed "*r_original;" in the question's code block to "let _ = *r_original;", it would compile. I was playing around and looks to me that when I do "let _ = r_original;", it would not do de-ref and thus it will not complain either even though the value has been moved indeed. – ggaowp Dec 19 '21 at 19:48
  • oh that's interesting! It does [remove the error](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d5cd25170e1d831be6abe2baca2bbe49). Sorry for misunderstanding, I've changed my vote. – kmdreko Dec 19 '21 at 20:00
  • thanks for the comment though, I have long been a pure consumer and learning to contribute a bit back :) – ggaowp Dec 19 '21 at 20:03