Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

First, I tried something like this:

let mut vec = vec![0];
vec.rotate_right(vec.len());

It can't be compiled because:

error[E0502]: cannot borrow `vec` as immutable because it is also borrowed as mutable

I thought that the Rust borrow checker could be smarter than this, so I found something called NLL, and it should solve this problem.

I tried the sample:

let mut vec = vec![0];
vec.resize(vec.len(), 0);

It could work, but why is it not working with rotate_right? Both of them take a &mut self. What's going on?

like image 717
Direktor Avatar asked Sep 01 '25 16:09

Direktor


1 Answers

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.

like image 69
Chayim Friedman Avatar answered Sep 04 '25 07:09

Chayim Friedman