It is definitely an interesting one.
They are similar - but not quite the same. resize()
is a member of Vec
. rotate_right()
, on the other hand, is a method of slices.
Vec<T>
derefs to [T]
, so most of the time this does not matter. But actually, while this call:
vec.resize(vec.len(), 0);
Desugars to something like:
<Vec<i32>>::resize(&mut vec, <Vec<i32>>::len(&vec), 0);
This call:
vec.rotate_right(vec.len());
Is more like:
<[i32]>::rotate_right(
<Vec<i32> as DerefMut>::deref_mut(&mut vec),
<Vec<i32>>::len(&vec),
);
But in what order?
This is the MIR for rotate_right()
(simplified a lot):
fn foo() -> () {
_4 = <Vec<i32> as DerefMut>::deref_mut(move _5);
_6 = Vec::<i32>::len(move _7);
_2 = core::slice::<impl [i32]>::rotate_right(move _3, move _6);
}
And this is the MIR for resize()
(again, simplified a lot):
fn foo() -> () {
_4 = Vec::<i32>::len(move _5);
_2 = Vec::<i32>::resize(move _3, move _4, const 0_i32);
}
In the resize()
example, we first call Vec::len()
with a reference to vec
. This returns usize
. Then we call Vec::resize()
, when we have no outstanding references to vec
, so mutably borrowing it is fine!
However, with rotate_right()
, first we call <Vec<i32> as DerefMut>::deref_mut(&mut vec)
. This returns &mut [i32]
, with its lifetime tied to vec
. That is, as long as this reference (mutable reference!) is alive, we are not allowed to use have any other reference to vec
. But then we try to borrow vec
in order to pass the (shared, but it doesn't matter) reference to Vec::len()
, while we still need to use the mutable reference from deref_mut()
later, in the call to <[i32]>::rotate_right()
! This is an error.
This is because Rust defines an evaluation order for operands:
Expressions taking multiple operands are evaluated left to right as written in the source code.
Because vec.resize()
is actually (&mut *vec).rotate_right()
, we first evaluate the dereference+reference, then the arguments:
let dereferenced_vec = &mut *vec;
let len = vec.len();
dereferencec_vec.rotate_right(len);
Which is obviously a violation of the borrow rules.
On the other hand, vec.resize(vec.len())
has no work to do on the callee (vec
), and so we first evaluate vec.len()
, and then the call itself.
Solving this is as easy as extracting the vec.len()
to a new line (new statement, to be precise), and the compiler also suggests that.