Reputation: 2515
I have two versions of a function that are intended to do the same thing.
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:
mut v_ref: &mut [i32]
, containing a mutable reference to a slicev_ref
using split_at_mut
*v_ref
is overwritten to hold one of the sub-slices*(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 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
{vref}
from E1) enforced for methods (i.e. split_at_mut
), but not functions (i.e., example2_inner
)? Why is this the case?example2_inner
as an independent utility function: how would I change Example 2 to accommodate that?Upvotes: 3
Views: 227
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
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