提问人:Bill Fraser 提问时间:6/25/2016 最后编辑:ShepmasterBill Fraser 更新时间:12/14/2021 访问量:6960
从 HashMap 或 Vec 返回引用会导致借用持续超出其所在的范围?
Returning a reference from a HashMap or Vec causes a borrow to last beyond the scope it's in?
问:
我有一个持续的编译错误,Rust 抱怨我在尝试可变借用时有一个不可变的借用,但不可变的借用来自另一个范围,我没有从中引入任何东西。
我有一些代码可以检查地图中的值,如果存在,则返回它,否则它需要以各种方式改变地图。问题是我似乎找不到一种方法让 Rust 让我同时做这两项操作,即使这两个操作是完全分开的。
下面是一些荒谬的代码,它们遵循与我的代码相同的结构并展示了问题:
use std::collections::BTreeMap;
fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
// extra scope in vain attempt to contain the borrow
{
// borrow immutably
if let Some(key) = map.get(&key) {
return Some(key);
}
}
// now I'm DONE with the immutable borrow, but rustc still thinks it's borrowed
map.insert(0, 0); // borrow mutably, which errors
None
}
此错误如下:
error[E0502]: cannot borrow `*map` as mutable because it is also borrowed as immutable
--> src/lib.rs:14:5
|
3 | fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
| - let's call the lifetime of this reference `'1`
...
7 | if let Some(key) = map.get(&key) {
| --- immutable borrow occurs here
8 | return Some(key);
| --------- returning this value requires that `*map` is borrowed for `'1`
...
14 | map.insert(0, 0); // borrow mutably, which errors
| ^^^^^^^^^^^^^^^^ mutable borrow occurs here
这对我来说没有任何意义。不可变的借用如何超越这个范围?!其中一个分支通过 退出函数,而另一个分支不执行任何操作并离开作用域。match
return
我以前见过这种情况,我错误地将借款偷运到其他变量的范围之外,但这里并非如此!
诚然,借用是通过语句逃避范围的,但荒谬的是,这阻止了函数中更深入的借用——程序不可能返回并继续前进!如果我在那里归还其他东西,错误就会消失,所以我认为这就是借用检查器挂断的东西。这对我来说就像一个错误。return
不幸的是,我无法找到任何方法在不遇到相同错误的情况下重写它,所以如果是这样的话,这是一个特别讨厌的错误。
答:
这是一个已知问题,将通过非词法生存期的未来迭代来解决,但目前尚未从 Rust 1.57 开始处理。
如果您要插入到您正在查找的同一键,我鼓励您改用入口 API。
您现在可以添加一点低效率来解决这个问题。如果效率低下是不可接受的,则有更深层次的解决方法。
HashMap
一般的想法是添加一个布尔值,告诉您某个值是否存在。这个布尔值不挂钩引用,所以没有借用:
use std::collections::BTreeMap;
fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
if map.contains_key(&key) {
return map.get(&key);
}
map.insert(0, 0);
None
}
fn main() {
let mut map = BTreeMap::new();
do_stuff(&mut map, 42);
println!("{:?}", map)
}
Vec
类似的情况可以通过使用元素的索引而不是引用来解决。与上面的情况一样,由于需要再次检查切片边界,这可能会带来一些低效率。
而不是
fn find_or_create_five<'a>(container: &'a mut Vec<u8>) -> &'a mut u8 {
match container.iter_mut().find(|e| **e == 5) {
Some(element) => element,
None => {
container.push(5);
container.last_mut().unwrap()
}
}
}
你可以写:
fn find_or_create_five<'a>(container: &'a mut Vec<u8>) -> &'a mut u8 {
let idx = container.iter().position(|&e| e == 5).unwrap_or_else(|| {
container.push(5);
container.len() - 1
});
&mut container[idx]
}
非词汇生存期
这些类型的示例是 NLL RFC 中的主要情况之一: 问题案例 #3:跨函数的条件控制流。
不幸的是,这个特定的案例在 Rust 1.57 中还没有准备好。如果您选择加入 nightly () 中的实验性功能,则这些原始示例中的每一个都会按原样编译:-Zpolonius
RUSTFLAGS="-Z polonius" cargo +nightly check
use std::collections::BTreeMap;
fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
if let Some(key) = map.get(&key) {
return Some(key);
}
map.insert(0, 0);
None
}
fn find_or_create_five(container: &mut Vec<u8>) -> &mut u8 {
match container.iter_mut().find(|e| **e == 5) {
Some(element) => element,
None => {
container.push(5);
container.last_mut().unwrap()
}
}
}
另请参阅:
-
在不返回引用的情况下,这是同样的问题,这确实适用于 Rust 1.32 中可用的 NLL 实现。
-
这个问题,但情况稍微复杂一些。
-
终极逃生舱口。
评论