CrepeGoat
CrepeGoat

Reputation: 2515

Why can I force reference move semantics for `&self` parameter of method, but not function parameters?

I have two versions of a function that are intended to do the same thing.

Version 1 - Works!

pub fn example1() {
    // Make a mutable slice
    let mut v = [0, 1, 2, 3];

    // Make a mutable reference to said slice
    let mut v_ref = &mut v[..];
    let len = v_ref.len();

    // Reduce slice to sub-slice -> np ;)
    v_ref = {
        // Create sub-slices
        let (v_l, v_r) = {
            // Involves some edits -> need mut
            v_ref.swap(0, len - 1);

            { v_ref }.split_at_mut(len / 2)
        };

        // Choose slice with which to overwrite
        // (involves some non-trivial condition here)
        match len % 2 {
            0 => v_l,
            _ => v_r,
        }
    };

    // And then we do something with v_ref
    println!("{:?}", v_ref);
}

Essentially:

*(Note - We avoid the problem of having two mutable references by moving v_ref, as opposed to reborrowing, by using an identity block)

(Regarding the intent of the code - this slice reduction is intended to be repeated in a loop; however this detail does not affect the problem)

Version 2 - Fails to compile

Version 2 is nearly identical to version 1, except that the sub-slice creation is moved to its own function:

fn example2_inner(v_ref: &mut [i32]) -> (&mut [i32], &mut [i32]) {
    // Recreate len variable in function scope
    let len = v_ref.len();

    // Same mutation here
    v_ref.swap(0, len - 1);

    // Same slice split here
    v_ref.split_at_mut(len / 2)
}

pub fn example2() {
    let mut v = [0, 1, 2, 3];

    let mut v_ref = &mut v[..];
    let len = v_ref.len();

    // This now fails to compile :(
    v_ref = {
        // Mutating & slice spliting moved to function
        let (v_l, v_r) = example2_inner({ v_ref });

        match len % 2 {
            0 => v_l,
            _ => v_r,
        }
    };

    println!("{:?}", v_ref);
}

When I try to build this, I get the following errors:

error[E0506]: cannot assign to `v_ref` because it is borrowed
  --> src/lib.rs:19:5
   |
19 | /     v_ref = {
20 | |         // Mutating & slice spliting moved to function
21 | |         let (v_l, v_r) = example2_inner({ v_ref });
   | |                                           ----- borrow of `v_ref` occurs here
22 | |
...  |
26 | |         }
27 | |     };
   | |_____^ assignment to borrowed `v_ref` occurs here

error[E0502]: cannot borrow `v_ref` as immutable because `*v_ref` is also borrowed as mutable
  --> src/lib.rs:29:22
   |
21 |         let (v_l, v_r) = example2_inner({ v_ref });
   |                                           ----- mutable borrow occurs here
...
29 |     println!("{:?}", v_ref);
   |                      ^^^^^ immutable borrow occurs here
30 | }
   | - mutable borrow ends here

Questions

Upvotes: 3

Views: 227

Answers (2)

Peter Hall
Peter Hall

Reputation: 58735

You can fix it like this:

v_ref = {
    // move `v_ref` to a new variable which will go out of scope by the end of the block
    let r = v_ref;
    // Mutating & slice splitting moved to function
    let (v_l, v_r) = example2_inner(r);

    match len % 2 {
        0 => v_l,
        _ => v_r,
    }
};

The reason is that v_1 and v_2 both are references to v_ref, but these references both outlive the block because they are being returned from it. Then you try to write to v_ref, which the borrow checker doesn't like because there are still these live references around.

Assigning v_ref to a new variable, r, means that v_ref is no longer valid - the variable has been moved. The live references, v_1 and v_2 are now referring to r, which in turn is a reference to v, and which only lives as long as the block. You are now free to write to v_ref because nothing else is referring to it.


Alternatively, you can just update to Rust Edition 2018, or enable non-lexical lifetimes, which can handle this situation.

Upvotes: 3

Calculator
Calculator

Reputation: 2789

Why don't these two examples compile the same way? Are mutable reference move semantics (i.e., the {vref} from E1) enforced for methods (i.e. split_at_mut), but not functions (i.e., example2_inner)? Why is this the case?

Actually, it is not about methods vs functions, but about method call syntax vs function call syntax.

Every method call can be translated into the UFCS (Universal Function Call Syntax) which is generally of this form:

<Type as Trait>::method(args);

If we make a naive attempt to translate the call of { v_ref }.split_at_mut(len / 2) into UFCS in version 1, we end up with the same error as in version 2:

<[_]>::split_at_mut({v_ref}, len / 2)

The reason is that the above is equivalent to something which again does not cause v_ref to be moved into the block:

<[_]>::split_at_mut({&mut *v_ref}, len / 2)

What the method syntax actually resolves to is this working variant of the above:

<[_]>::split_at_mut(&mut *{v_ref}, len / 2)

For this variant, the compiler infers that v_ref indeed should be moved into the block, because the compiler realizes that the re-borrowing necessary for the method call is already going to happen on {v_ref}, so it doesn't insert an additional superfluous re-borrow on v_ref.

Now that we know how the method syntax solves your issue implicitly, we have an alternative solution for your issue in version 2:

example2_inner(&mut *{ v_ref });

Upvotes: 2

Related Questions