Why is Rust NLL not working for multiple borrows in the same statement?

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.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)