为什么 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()
之前。
首先,我尝试了这样的事情:
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()
之前。