借用中间变量可绕过的错误

Borrowing errors bypassable with an intermediate variable

我正在尝试在 Rust 中实现一个简单的分桶哈希 table(仅供练习)。哈希 table 结构定义为:

pub struct BucketedHashTable<K: Hash, V> {
    buckets: Vec<Bucket<K, V>>,
    size: usize,
}

其中 Bucket 是我对单链接堆栈的简单实现。

在 table(putremoveget)的几乎所有方法中,我将获取要插入密钥的存储桶(从中删除,查找),所以我为此提取了一个方法:

    fn pick_bucket(&mut self, key: K) -> &mut Bucket<K, V> {
        let mut hasher = DefaultHasher::new();
        key.hash(&mut hasher);
        let hash = hasher.finish() as usize;
        let index = hash % self.buckets.len();
        &mut self.buckets[index]
    }

我 return 对存储桶的引用,因为我不想将它移出散列 table 的 buckets Vec。我 return 一个 mutable 引用,因为我要改变 returned 桶(例如,当向其中插入一个新条目(键值对)时)。

以上代码编译通过。

但是 如果我去掉中间变量 index 并计算 [] 括号内的索引,就像这样:

    fn pick_bucket(&mut self, key: K) -> &mut Bucket<K, V> {
        let mut hasher = DefaultHasher::new();
        key.hash(&mut hasher);
        let hash = hasher.finish() as usize;
        &mut self.buckets[hash % self.buckets.len()]
    }

我会得到这个借用错误:

error[E0502]: cannot borrow `self.buckets` as immutable because it is also borrowed as mutable
  --> src\lib.rs:30:34
   |
26 |     fn pick_bucket(&mut self, key: K) -> &mut Bucket<K, V> {
   |                    - let's call the lifetime of this reference `'1`
...
30 |         &mut self.buckets[hash % self.buckets.len()]
   |         -------------------------^^^^^^^^^^^^^^^^^^-
   |         |    |                   |
   |         |    |                   immutable borrow occurs here
   |         |    mutable borrow occurs here
   |         returning this value requires that `self.buckets` is borrowed for `'1`

我认为上面的两个代码片段是等价的。它们有何不同,为什么第一个可以编译,为什么第二个不能?

编辑: 我想第一个代码片段中 self.buckets 的 immutable 借用超出了范围并在行尾有 let index = ...;,所以当方法 returns 时,没有对 self.buckets 的共享引用;在第二个片段中,self.buckets 的 immutable 借用一直存在到 returns 方法(所以在那一刻,共享和 mutable 参考合作存在)。如果这是正确的,为什么会这样?为什么在第二种情况下到达 ] 括号时 self.buckets 的 immutable 借用不会“失效”,而是在整个表达式(整个 return 行)?

问题是你在 self.buckets.len() 时借用 self 作为不可变的。但是在你 return a &mut 的同一行中。检查这个更简单的例子:

struct MyVec(Vec<u8>);

impl MyVec {
    fn get_last_mut(&mut self) -> &mut u8 {
        &mut self.0[self.0.len() - 1]
    }
}

Playground

编译器不够聪明,无法知道哪个借用引用优先。您可以像以前一样强制执行优先级(之前使用一个):

struct MyVec(Vec<u8>);

impl MyVec {
    fn get_last_mut(&mut self) -> &mut u8 {
        let i = self.0.len() - 1;
        &mut self.0[i]
    }
}

Playground

Notice that you can not use a & meanwhile you have a &mut, so either use it before, or you will need to drop the &mut before using an & again.

我相信您已经完全解决了您编辑中的问题。在第一个示例中,self.buckets 被不可变地借用,然后借用在语句末尾结束。在第二个示例中,self.buckets 被可变地借用,然后在同一语句中被不变地借用。这是不允许的。

self.buckets.len()是临时的,其生命周期规则(drop scope)在Temporary Scopes:

中有解释

Apart from lifetime extension, the temporary scope of an expression is the smallest scope that contains the expression and is one of the following:

  • The entire function body.
  • A statement.
  • The body of a if, while or loop expression.
  • The else block of an if expression.
  • The condition expression of an if or while expression, or a match guard.
  • The expression for a match arm.
  • The second operand of a lazy boolean expression.

这在注释中有进一步解释:

Temporaries that are created in the final expression of a function body are dropped after any named variables bound in the function body, as there is no smaller enclosing temporary scope.

所以 self.buckets.len() 借用一直持续到函数的末尾(在函数体之后,甚至在语句完成之后)。但更容易认为它持续到语句的末尾(在任何情况下都会如此)。

在单个语句中没有创建“子表达式”放置范围。理论上这可能是可能的,但 Rust 并没有那么激进。