借用中间变量可绕过的错误
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(put
、remove
、get
)的几乎所有方法中,我将获取要插入密钥的存储桶(从中删除,查找),所以我为此提取了一个方法:
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]
}
}
编译器不够聪明,无法知道哪个借用引用优先。您可以像以前一样强制执行优先级(之前使用一个):
struct MyVec(Vec<u8>);
impl MyVec {
fn get_last_mut(&mut self) -> &mut u8 {
let i = self.0.len() - 1;
&mut self.0[i]
}
}
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 并没有那么激进。
我正在尝试在 Rust 中实现一个简单的分桶哈希 table(仅供练习)。哈希 table 结构定义为:
pub struct BucketedHashTable<K: Hash, V> {
buckets: Vec<Bucket<K, V>>,
size: usize,
}
其中 Bucket
是我对单链接堆栈的简单实现。
在 table(put
、remove
、get
)的几乎所有方法中,我将获取要插入密钥的存储桶(从中删除,查找),所以我为此提取了一个方法:
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]
}
}
编译器不够聪明,无法知道哪个借用引用优先。您可以像以前一样强制执行优先级(之前使用一个):
struct MyVec(Vec<u8>);
impl MyVec {
fn get_last_mut(&mut self) -> &mut u8 {
let i = self.0.len() - 1;
&mut self.0[i]
}
}
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 并没有那么激进。