在嵌套的数组索引中,“不能作为不可变借用,因为它也被作为可变借用”是什么意思?

18

在这种情况下,这个错误是什么意思:

fn main() {
    let mut v: Vec<usize> = vec![1, 2, 3, 4, 5];
    v[v[1]] = 999;
}
error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:3:7
  |
3 |     v[v[1]] = 999;
  |     --^----
  |     | |
  |     | immutable borrow occurs here
  |     mutable borrow occurs here
  |     mutable borrow later used here

我发现通过IndexIndexMut traits实现了索引,并且v[1]是语法糖,等价于*v.index(1)。拥有这个知识后,我尝试运行以下代码:

use std::ops::{Index, IndexMut};

fn main() {
    let mut v: Vec<usize> = vec![1, 2, 3, 4, 5];
    *v.index_mut(*v.index(1)) = 999;
}

令我惊讶的是,这个方法完美地运行起来了!为什么第一个代码片段不起作用,但第二个可以?按照文档的理解,它们应该是等效的,但显然情况并非如此。


2
学习 Rust 并参加 Advent of Code?欢迎来到 StackOverflow,并感谢您提出了这个好问题! - Sven Marnach
准确地说,这是我第三年从事编程(之前有两年的Haskell经验)~> 因为我对底层技术越来越感兴趣,所以想尝试一下Rust。 - Lucas Boucke
@LucasBoucke 这很有趣,我通常在我的项目中使用Rust,但我用Haskell编写了这个AoC。它们都是各自领域的伟大语言。 - Boiethios
1个回答

17

去糖语法版本与您所拥有的略有不同。该行

v[v[1]] = 999;

实际上等同于

*IndexMut::index_mut(&mut v, *Index::index(&v, 1)) = 999;

这会导致相同的错误消息,但注释提供了一些提示,让我们知道正在发生什么:

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:48
  |
7 |     *IndexMut::index_mut(&mut v, *Index::index(&v, 1)) = 999;
  |      ------------------- ------                ^^ immutable borrow occurs here
  |      |                   |
  |      |                   mutable borrow occurs here
  |      mutable borrow later used by call
重要的区别在于求值顺序。函数调用的参数按照从左到右的顺序进行求值,然后再实际进行函数调用。在本例中,这意味着首先评估&mut v,对v进行可变借用。接下来应该评估Index::index(&v, 1),但这是不可能的——v已经被可变地借用了。最后编译器显示需要可变引用才能调用index_mut()函数,因此当尝试使用共享引用时,可变引用仍然存在。
实际编译的版本具有略微不同的求值顺序。
*v.index_mut(*v.index(1)) = 999;

首先,方法调用中的函数参数从左到右进行评估,即*v.index(1)首先被评估。这将导致一个usize,并且可以释放v的临时共享借用。然后,接收器index_mut()被评估,即可对v进行可变借用。这很好地解决了问题,因为共享借用已经完成,整个表达式通过了借用检查。

请注意,仅编译版本之所以能够编译,是由于“非词法生命期”的引入。在Rust的早期版本中,共享借用将持续到表达式的结束,并导致类似的错误。

我认为最清晰的解决方案是使用临时变量:

let i = v[1];
v[i] = 999;

哇!这里有很多东西要处理!感谢您花时间解释!(有趣的是,这些“怪癖”让我对语言更感兴趣...)。您能否给出一个提示,为什么 *v.index_mut(*v.index_mut(1)) = 999; 会失败并显示“无法借用v作为可变的超过一次”?难道编译器不能像 *v.index_mut(*v.index(1)) = 999; 那样找出内部借用不再需要了吗? - Lucas Boucke
@LucasBoucke Rust确实有一些怪癖,有时可能有点不方便,但在大多数情况下,解决方案相当简单,就像这种情况一样。代码仍然非常易读,只是与您最初拥有的略有不同,因此实际上并不是什么大问题。 - Sven Marnach
@LucasBoucke 抱歉,我直到现在才看到你的编辑。*v.index(1) 的结果是存储在该索引处的 _值_,并且该值不需要保持 v 的借用。另一方面,*v.index_mut(1) 的结果是一个 _可变的位置表达式_,理论上可以进行赋值,因此它会保持借用。从表面上看,应该可以教会借用检查器,在值表达式上下文中,将位置表达式视为值表达式处理,因此在 Rust 的某个未来版本中可能会编译通过。 - Sven Marnach
怎么样,我们来写一个 RFC 把它转化为这样:{ let index = *Index::index(&v, 1); let value = 999; *IndexMut::index_mut(&mut v, index) = value; } - Boiethios
@FrenchBoiethios 我不知道你如何正式化它,而且我确定它永远不会起飞。如果你想解决这个问题,我唯一能想到的方法是通过改进借用检查器来实现,例如使其检测可变借用可以在稍后的时间开始,因为它并不是那么早就需要了。(这个特定的想法可能也行不通。) - Sven Marnach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接