return语句导致Rust似乎同时具有可变引用和共享引用

bf1o4zei  于 2022-12-19  发布在  其他
关注(0)|答案(1)|浏览(99)

下面的代码:

fn main() {
    let mut v = vec![1, 2, 3];
    for _ in v.iter() {
        v[0] = 0;
        // return;
    }
}

它不能编译,我知道为什么了,因为我试图同时拥有v的共享(v.iter())和可变(v[0] = 0)引用。
然而,取消注解return语句会使代码编译,我不知道为什么,我猜编译器不知何故设法知道在可变借位发生之前可以删除对v的共享引用,但它是如何确切地知道这一点的呢?
在本例中,v的两次借用的生存期是多少?

2wnc66cl

2wnc66cl1#

for循环只是带有手动迭代器的循环的语法糖:

for x in c {
   //...
}

相当于:

let mut __iterator = c.into_iter();
while let Some(x) = __iterator.next() {
   //...
}

因此,对于原始代码,它将是:

fn main() {
    let mut v = vec![1, 2, 3];
    let mut __iterator = v.iter().into_iter();
    while let Some(_) = __iterator.next() {
        v[0] = 0;
        // return;
    }
}

(The这里对into_iter()的调用是多余的,因为将其与迭代器一起使用只会返回自身。)
正如预期的那样,这段代码和您的代码一样失败。
但是如果您取消对返回的注解,那么编译器就知道循环永远不会重复,因此它被转换为:

fn main() {
    let mut v = vec![1, 2, 3];
    let mut __iterator = v.iter().into_iter();
    if let Some(_) = __iterator.next() {
        v[0] = 0;
    }
}

现在,* 非词法生存期(NLL)*,并看到iterator在调用next()之外没有被使用,因此可以将其删除。_不算在内,因为它不是真实的的绑定,但即使您改为编写_x,NLL会立即丢弃它,因为它不再被使用,因此v不再是借用的,对v[0]的赋值是完全安全和法律的的。
如果您保持v的原始借位有效,它将再次失败:

fn main() {
    let mut v = vec![1, 2, 3];
    let mut __iterator = v.iter().into_iter();
    if let Some(x) = __iterator.next() {
        v[0] = 0; // error! v is still borrowed
        dbg!(x);
    }
}

这个失败有一个很好的解释:

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:9
  |
3 |     let mut __iterator = v.iter().into_iter();
  |                          -------- immutable borrow occurs here
4 |     if let Some(x) = __iterator.next() {
5 |         v[0] = 0;
  |         ^ mutable borrow occurs here
6 |         dbg!(x);
  |              - immutable borrow later used here

相关问题