Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the mutable reference not moved here?

Tags:

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

like image 431
jtepe Avatar asked Aug 22 '15 09:08

jtepe


People also ask

What is a mutable reference?

A mutable reference is a borrow to any type mut T , allowing mutation of T through that reference. The below code illustrates the example of a mutable variable and then mutating its value through a mutable reference ref_i . fn main() {

What is Reborrow in Rust?

What is Borrowing? When a function transfers its control over a variable/value to another function temporarily, for a while, it is called borrowing. This is achieved by passing a reference to the variable (& var_name) rather than passing the variable/value itself to the function.

Does Rust have references?

In Rust, by contrast, the compiler guarantees that references will never be dangling references: if you have a reference to some data, the compiler will ensure that the data will not go out of scope before the reference to the data does.


1 Answers

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.

like image 80
Veedrac Avatar answered Sep 23 '22 07:09

Veedrac