Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do mutable references have move semantics?

fn main() {
    let mut name = String::from("Charlie");
    let x = &mut name;
    let y = x;       // x has been moved
    say_hello(y);
    say_hello(y);       // but y has not been moved, it is still usable
    change_string(y);
    change_string(y);  

}

fn say_hello(s: &str) {
    println!("Hello {}", s);
}

fn change_string(s: &mut String) {
    s.push_str(" Brown");
}

When I assign x to y x has been moved. However, I would expect something with move semantics to be moved when I use it in a function. However, I can still use the reference after subsequent calls. Maybe this has to do with say_hello() taking a immutable reference but change_string() takes a mutable reference but the reference is still not moved.

like image 593
Rafael Avatar asked Jul 17 '20 19:07

Rafael


People also ask

What is a mutable reference?

A mutable type is a type whose instance data can be modified. The System. Text. StringBuilder class is an example of a mutable reference type. It contains members that can change the value of an instance of the class.

What is move in Rust?

When doing assignments ( let x = y ) or passing function arguments by value ( foo(x) ), the ownership of the resources is transferred. In Rust-speak, this is known as a move. After moving resources, the previous owner can no longer be used.


1 Answers

You are completely right with both your reasoning and your observations. It definitely looks like things should be happening the way you describe it. However, the compiler applies some convenience magic here.

Move semantics generally apply in Rust for all types that do not implement the Copy trait. Shared references are Copy, so they are simply copied when assigned or passed to a function. Mutable references are not Copy, so they should be moved.

That's where the magic starts. Whenever a mutable reference is assigned to a name with a type already known to be a mutable reference by the compiler, the original reference is implicitly reborrowed instead of being moved. So the function called

change_string(y);

is transformed by the compiler to mean

change_string(&mut *y);

The original reference is derefenced, and a new mutable borrow is created. This new borrow is moved into the function, and the original borrow gets released once the function returns.

Note that this isn't a difference between function calls and assignments. Implicit reborrows happen whenever the target type is already known to be a mutable reference by the compiler, e.g. because the pattern has an explicit type annotation. So this line also creates an implicit reborrow, since we explicitly annotated it as a mutable reference type:

let y: &mut _ = x;

This function call on the other hand moves (and thus consumes) the mutable reference y:

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

[...]
foo(y);

The generic type T here isn't explicitly a mutable reference type, so no implicit reborrow occurs, even though the compiler infers that the type is a mutable reference – just as in the case of your assignment let y = x;.

In some cases, the compiler can infer a generic type is a mutable reference even in the absence of an explicit type annotation:

fn bar<T>(_a: T, _b: T) {}

fn main() {
    let mut i = 42;
    let mut j = 43;
    let x = &mut i;
    let y = &mut j;
    bar(x, y);   // Moves x, but reborrows y.
    let _z = x;  // error[E0382]: use of moved value: `x`
    let _t = y;  // Works fine. 
}

When inferring the type of the first parameter, the compiler doesn't know yet it's a mutable reference, so no implicit reborrow occurs and x is moved into the function. However, when reaching the second parameter, the compiler has already inferred that T is a mutable reference, so y is implicitly reborrowed. (This example is a good illustration why adding compiler magic to make things "just work" generally is a bad idea. Explicit is better than implicit.)

Unfortunately, this behaviour currently isn't documented in the Rust reference.

See also:

  • Stuff the Identity Function Does (in Rust)
  • Discussion of the topic on the Rust users forum
  • Why is the mutable reference not moved here?
like image 109
Sven Marnach Avatar answered Oct 07 '22 12:10

Sven Marnach