为什么 Rust NLL 不适用于同一语句中的多次借用?

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

首先,我尝试了这样的事情:

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

无法编译因为:

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

我认为 Rust 借用检查器可能比这更聪明,所以我找到了一个叫做 NLL 的东西,它应该可以解决这个问题。

我试过样本:

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

它可以工作,但为什么它不能与 rotate_right 一起工作?两人都拿了一个&mut self。怎么回事?

这绝对是一个有趣的。

它们很相似,但又不完全相同。 resize() is a member of Vec. rotate_right(),另一方面,是一种切片方法。

Vec<T> derefs to [T],所以大多数时候这并不重要。但实际上,虽然这个电话:

vec.resize(vec.len(), 0);

脱糖成类似的东西:

<Vec<i32>>::resize(&mut vec, <Vec<i32>>::len(&vec), 0);

这次通话:

vec.rotate_right(vec.len());

更像是:

<[i32]>::rotate_right(
    <Vec<i32> as DerefMut>::deref_mut(&mut vec),
    <Vec<i32>>::len(&vec),
);

但是顺序是什么?

这是rotate_right()MIR(简化了很多):

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);
}

这是 resize() 的 MIR(再次简化了很多):

fn foo() -> () {
    _4 = Vec::<i32>::len(move _5);
    _2 = Vec::<i32>::resize(move _3, move _4, const 0_i32);
}

resize() 示例中,我们首先调用 Vec::len() 并引用 vec。这个returnsusize。然后我们调用 Vec::resize(),当我们没有对 vec 的突出引用时,所以可变地借用它就可以了!

然而,对于 rotate_right(),我们首先调用 <Vec<i32> as DerefMut>::deref_mut(&mut vec)。这个 returns &mut [i32],其生命周期与 vec 相关联。也就是说,只要这个引用(可变引用!)还活着,我们就不能使用对 vec 的任何其他引用。但是然后我们尝试借用 vec 以便将(共享,但无所谓)引用传递给 Vec::len(),而我们稍后仍然需要使用来自 deref_mut() 的可变引用, 在调用 <[i32]>::rotate_right()!这是一个错误。

这是因为 Rust 定义了 an evaluation order for operands:

Expressions taking multiple operands are evaluated left to right as written in the source code.

因为vec.resize()实际上是(&mut *vec).rotate_right(),我们先计算dereference+reference,然后是arguments:

let dereferenced_vec = &mut *vec;
let len = vec.len();
dereferencec_vec.rotate_right(len);

这显然违反了借用规则。

另一方面,vec.resize(vec.len()) 对被调用者 (vec) 没有任何工作要做,所以我们首先评估 vec.len(),然后是调用本身。

解决这个问题就像将 vec.len() 提取到一个新行(准确地说是新语句)一样简单,编译器也建议这样做。

这两个调用之间唯一的结构差异是目标:rotate_right() 是在切片上定义的,而 resize() 是在 Vec 上定义的。这意味着 non-working 案例有一个额外的 Deref 强制转换(在本例中 DerefMut)必须通过借用检查器。

这实际上超出了 non-lexical 生命周期规则的范围,因为 引用的生命周期 并不重要。这种好奇心属于 evaluation order 规则;更具体地说,给定 vec.rotate_right(vec.len()),从 &mut Vec<_>&mut [_] 的强制转换何时发生?在 vec.len() 之前还是之后?

函数和方法调用的求值顺序是left-to-right,所以强制转换必须在其他参数之前求值,这意味着vec已经是可变借用的在调用 vec.len() 之前。