为什么HashMap::get_mut()在其余作用域内拥有map的所有权?

14

我有以下代码,它将一些值插入到HashMap中,然后再取出它们:

use std::collections::HashMap;

fn things() {
    let mut map = HashMap::new();
    map.insert(5, "thing");
    map.insert(4, "world");
    map.insert(1, "hello");
    let mut thing = map.remove(&5);
    let mut world = map.get_mut(&4);
    let mut hello = map.get_mut(&1);
}

尝试编译此代码将产生以下错误:

error[E0499]: cannot borrow `map` as mutable more than once at a time
  --> src/main.rs:10:21
   |
9  |     let mut world = map.get_mut(&4);
   |                     --- first mutable borrow occurs here
10 |     let mut hello = map.get_mut(&1);
   |                     ^^^ second mutable borrow occurs here
11 | }
   | - first borrow ends here

在阅读了remove()get_mut()方法的API文档后(幸运的是它们非常接近!),从方法签名中没有任何显著的原因可以说明为什么remove()方法不会在当前作用域的其余时间内可变地借用映射,而get_mut()方法会。

另一个让我感到困惑的数据片段是这段代码编译成功:

use std::collections::HashMap;

fn things() {
    let mut map = HashMap::new();
    map.insert(5, "thing");
    map.insert(4, "world");
    map.insert(1, "hello");
    let mut thing = map.remove(&5);
    map.get_mut(&4);
    let mut hello = map.get_mut(&1);
}

不存储对get_mut()的第一次调用结果不会导致地图在其余范围内被可变借用?我从文档中怎么能知道这一点呢?难道我还错过了其他什么吗?


请注意,这是与 http://stackoverflow.com/q/32761370/155423 相同的根本问题。 - Shepmaster
1个回答

20

这个错误是在非词法生命周期实现之前借用检查器的一个限制。启用后,原始代码将按原样工作:

use std::collections::HashMap;

fn things() {
    let mut map = HashMap::new();
    map.insert(5, "thing");
    map.insert(4, "world");
    map.insert(1, "hello");
    let mut thing = map.remove(&5);
    let mut world = map.get_mut(&4);
    let mut hello = map.get_mut(&1);
}

fn main() {}
这是因为编译器更加智能,可以看到在到达 map.get_mut(&1) 时您不再使用 world,所以它不需要再保持有效的引用。
您可以通过添加显式作用域,在 Rust 的早期版本中获得等效的代码:
let mut thing = map.remove(&5);
{
    let mut world = map.get_mut(&4);
}
let mut hello = map.get_mut(&1);

为什么HashMap::get_mut()会获取 Map 的所有权?

它绝对不会这样做。在Rust代码中,所有权是一个精确的术语。请注意,错误消息明确指出:

先前借用了map

借用并不是所有权。如果我借你的车,我并没有拥有你的车。

你真正的问题是“为什么它会在其余作用域中借用它”。让我们看一下函数签名:

fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V> 
where
    K: Borrow<Q>,
    Q: Hash + Eq,

简述一下,这段代码的意思是:

给定一个可变引用指向一个HashMap&mut self),以及可以用来查找键的东西(K: Borrow<Q>, Q: Hash + Eq),如果有匹配的值,则返回对该值的可变引用(Option<&mut V>)。

然而,所返回的可变引用将会改变HashMap 中的某些东西,这就是为什么它是可变引用的原因。您一次只能拥有多个不可变借用或一个可变借用。这可以防止编写导致不一致和安全问题的代码。

现在来看看remove函数:

fn remove<Q: ?Sized>(&mut self, k: &Q) -> Option<V> 
where
    K: Borrow<Q>, 
    Q: Hash + Eq,

这将返回一个拥有所有权的值,而不是一个对HashMap的引用。一旦方法完成,地图的借用就结束了。


2
我想我明白了。当你在结构体上有一个可变借用self的方法,并且该方法返回一个可变引用时,唯一有效的假设是该可变引用指向该结构体上的某个字段,该字段传递性地保持了对结构体的借用。感谢澄清! - Ian Andrews
1
值得注意的是,生命周期在这里起到了作用,请参见http://stackoverflow.com/a/32768690/81896。 - Michał Bendowski
@MichałBendowski 当然没问题。只是在这种情况下,“生命周期省略”将self和返回值的生命周期绑定在一起,因此您不必显式地编写它。 - Shepmaster

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