34

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?

Pang
  • 9,564
  • 146
  • 81
  • 122
Direktor
  • 491
  • 2
  • 8
  • 3
    The difference is that the first one desugars to `[_]::rotate_right (DerefMut::deref_mut (&mut vec), Vec::len (&vec))` while the second one desugars to `Vec::Resize (&mut vec, Vec::len (&vec), 0`. The extra call to `deref_mut` is probably the reason why the borrow checker refuses the first case. – Jmb Apr 11 '22 at 06:52

2 Answers2

30

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.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
8

The only structural difference between these two calls is the target: rotate_right() is defined on a slice, while resize() is defined on a Vec. This means that the non-working case has an additional Deref coercion (in this case DerefMut) that must pass the borrow checker.

This is actually outside the realm of non-lexical lifetime rules since how long the references live is not important. This curiosity would fall under evaluation order rules; more specifically, given vec.rotate_right(vec.len()), when does the coercion from &mut Vec<_> to &mut [_] occur? Before or after vec.len()?

Evaluation order for function and method calls is left-to-right, so the coercion must be evaluated before the other parameters, meaning vec is already mutably borrowed before vec.len() is called.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • 1
    This evaluation order bugs me a lot by making me write a single line of code in two lines. Any example of where this order is beneficial or evaluating parameters first is unsafe ? – Gurwinder Singh Apr 11 '22 at 07:10
  • 5
    @GurwinderSingh honestly, evaluating the callee last wouldn't make sense. A method chain like this `self.f()?.g()?.calculate(expensive_call())` is equivalent to `Type::calculate(self.f()?.g()?, expensive_call())` so `self.f()?.g()?` is the expression for the callee, but you want `expensive_call()` to be evaluated first? Even when there are early-exits available from the `?` operator? Expressions can be complicated, so its more reasonable to have rules set to match the programmers' expectations: that code roughly runs in the order it is written. You can re-order it yourself if needed. – kmdreko Apr 11 '22 at 07:20
  • 2
    @kmdreko OTOH it's unintuitive because `resize` does work, even though if the callee is evaluated first it "should". In fact looking at the MIR it first takes a mutable reference to the vector, *then takes an immutable one and gets the length*. I guess this works fine because that doesn't actually call anything and doesn't cross a basic block boundary, but still it does make for a very unintuitive behaviour. – Masklinn Apr 11 '22 at 07:54
  • @Masklinn oh I agree. Just because there's a reason for it doesn't mean it isn't unintuitive. – kmdreko Apr 11 '22 at 08:10
  • @Masklinn Yes, IMO if methods like `resize` could work, means that the evaluation of `&mut self` happens later. If it's designed deliberately, there's no reason to not do the same for `Deref`. – Direktor Apr 11 '22 at 09:54
  • I guess, in a way, it's consistent. In `foo.bar(foo.baz())`, first callee is resolved i.e. `foo.bar` and then arg `foo.baz()` and hence disallowed. – Gurwinder Singh Apr 12 '22 at 02:47