Questions about `&mut T` and move semantics, `&mut T` is "move-only"?

While the lifetime is an aspect of the type not all aspects of a type are resolved at the same time. If I understand @trentj's post correctly deref coercion has already taken place by the time lifetimes are even considered.

Also consider the following examples:

fn f2<'a>(x: &'a mut i32) {
    let y: &'a mut i32 = x;

    x; // Does not compile. Cannot move out of `x` because it is borrowed.
}

I can easily rationalize that particular compilation error because stacked borrows cannot accomodate a situation where the lifetime of x and y have to be equal - the lifetime of y has to be strictly less than the lifetime of x.


To recap:

// Example 4a - the original puzzle
fn f(z: &mut i32) {
    *z += 1;
    println!("{}", *z);
}

fn main() {
    let mut x = 5i32; // `x` binds to a value of type i32
    let y = &mut x;   // `y` binds to a value of type &i32 with exclusive access to `x`
    f(y);

    // Rigorously applying move semantics only, the `y` binding should now be "uninitialized".
    // The `&i32` value with exclusive access (i.e. `&mut i32`) has been
    // moved into `f` - never to be seen again.
    // However the next line of code successfully uses the `y` binding.
    // If the value had been moved there should be a compilation error.
    //
    // Also as Example 3 showed a `&mut i32` value isn't Copy
    // (i.e. has move semantics rather than copy semantics)
    // even though the `i32` value is Copy (i.e. has copy semantics)

    *y += 1;
    println!("{}", x); // '7'
}

As @trentj outlined your confusion wasn't entirely unjustified - using generics:

// Example 4b - using parameterized types
use std::fmt::Display;
use std::marker::Sized;
use std::ops::{AddAssign, DerefMut};

fn f<T>(mut z: T, v: T::Target) where
    T: DerefMut,
    T::Target: Display + Sized + AddAssign {
    *z += v;
    println!("{}", *z);
}

fn main() {
    let mut x = 5i32; // `x` binds to a value of type i32
    let y = &mut x;   // `y` binds to a value of type &i32 with exclusive access to `x`
    f(y, 1);

    // *y += 1;
    // ^ uncomment the above and get the following compiler error:
    // move occurs because `y` has type `&mut i32`, which does not implement the `Copy` trait
    println!("{}", x); // '6'
}

The actual problem - people coming other programming languages expect Example 4a to work because that's the way references tend to work in traditional languages. In the absence of implicit borrows we would have to write:

// Example 4c - in a world without implicit borrows
fn f(z: &mut i32) {
    *z += 1;
    println!("{}", *z);
}

fn main() {
    let mut x = 5i32; // `x` binds to a value of type i32
    let y = &mut x;   // `y` binds to a value of type &i32 with exclusive access to `x`
    f(&mut *y);
    // i.e. forcing a reborrow to make things work

    *y += 1;
    println!("{}", x); // '7'
}

At this point many developers coming from traditional languages would call f(&mut *y); unergonomic, even though it's entirely consistent with move semantics.

So the implicit borrow present in Example 4a is a concession to developer ergonomics - at least for functions that don't use parameterized types (there the rules are a bit different).


Aside: I think going forward I'm going to classify features like implicit borrows and deref coercion as "you know what I mean programming support".

For example, I would have viewed manual deref in the presence of type inference as a kind of programmer's dead man's switch, a checkpoint system where the compiler could zap me awake because obviously I've stopped paying attention to the details (Bugs Abhor Loneliness).

1 Like