我正在使用HashMap
,但我在如何发布"发现" HashMap
的可变借用,无法找到如何做到的好解释。
这只是一个例子,目标不是解决问题"而是要了解如何实现这一目标和/或为什么不应该这样做。
该示例包含HashMap
存储一些简单的Record
s:
type Map = HashMap<String, Record>;
pub struct Record {
pub count: u32,
pub name: String,
}
impl Record {
fn new<S: Into<String>>(name: S) -> Record {
Record { name: name.into(), count: 0 }
}
pub fn add<'a>(&'a mut self, inc: u32) -> &'a mut Record {
self.count += inc;
self
}
}
add
函数在记录中有一个可变函数,但不是真正的罪魁祸首。
我们现在想要实现一个函数,该函数返回Record
中HashMap
的引用,以便我们可以就地修改它。除此之外,我们希望能够控制返回的引用,以便我们可以做一些副作用(对于这个例子,我们假设我们想要打印出正在发生的事情就足够了,但它可能是一些处理统计和/或访问其他存储或进行延迟评估的其他操作)。为了解决这个问题,我们引入了一个Handle
结构,它保留了对Record
的引用以及对该记录来自的HashMap
的引用。
pub struct Handle<'a> {
map: &'a Map,
record: &'a Record,
}
impl<'a> Handle<'a> {
fn new(record: &'a Record, map: &'a Map) -> Handle<'a> {
println!("Retrieving record");
Handle { record: record, map: map }
}
fn mut_record(&mut self) -> &mut Record {
println!("Modifying record");
self.record
}
}
让我们假设我们出于某种原因需要两个引用,并注意我们可以在句柄存在时保留对HashMap
的不可变借位,因此不需要修改{{1应该发生。
HashMap
只是暂时的,我们希望它可以像这样大致使用:
Handle
let mut map = HashMap::new();
let foo = get_or_insert(&mut map, "foo");
foo.mut_record().do_something(|record| record.add(3))
的第一个实现是:
get_or_insert
这会出现以下错误:
pub fn get_or_insert<'a, S>(map: &'a mut Map, name: S) -> Handle<'a>
where S: Into<String>
{
let key = name.into();
let record = map.entry(key.clone()).or_insert(Record::new(key));
Handle::new(record, map)
}
error[E0502]: cannot borrow `*map` as immutable because it is also borrowed as mutable
--> hashmap.rs:65:29
|
64 | let record = map.entry(key.clone()).or_insert(Record::new(key));
| --- mutable borrow occurs here
65 | Handle::new(record, map)
| ^^^ immutable borrow occurs here
66 | }
| - mutable borrow ends here
有两个引用,第一个是可变借用。我们需要&#34;发布&#34;在我们可以获取不可变借入之前,第一个可变借用地图。我尝试以这种方式编写代码,并在第一个可变借用期间添加了一个范围,期望它是&#34;已发布&#34;当范围结束时:
HashMap
但错误仍然存在。
非常奇怪的是,即使在范围结束后,可变借款仍然存在。根据{{3}},借用应该在范围的末尾结束,并且根据References and Borrowing范围由块控制,这些块是括在括号中的语句的集合,所以从表面上看,似乎是后面的函数定义应该通过对地图的引用的可变借用来结束范围。
如何以合理的方式实现这样的pub fn get_or_insert<'a, S>(map: &'a mut Map, name: S) -> Handle<'a>
where S: Into<String>
{
let key = name.into();
let record = {
map.entry(key.clone()).or_insert(Record::new(key))
};
Handle::new(record, map)
}
,以便Handle
的生命周期不超过Handle
的生命周期并在编译时捕获它?我正在寻找一种创建HashMap
使用临时Handle
作为实现提供的抽象,抽象出对底层存储的访问。
在编译时捕获误用而不是运行时,这会导致Handle
和RefCell
取消资格。
在基础结构中执行单个查找。
我查看了Rc
但是将检查从编译时移到了运行时,能够在编译时捕获RefCell
的误用是有益的。
Scope and shadowing中的问题与此问题类似,但答案是使用Handle
,它解决了检查而不是解决问题。
对我来说,问题似乎是需要有一种方法将可变引用转换为不可变引用并释放可变引用(限制它应该被代码允许),但仍然不确定是否我误解了一些事情。
更新:此问题最初有三个子弹试图使其更具结构性,但它已被重写为只提出一个问题,以明确目标是什么是
答案 0 :(得分:1)
在这一行:
let record = map.entry(key.clone()).or_insert(Record::new(key));
record
的类型为&'a mut Record
,因为or_insert
会返回对HashMap
中存储的值的可变引用。这使借用map
保持活跃;这就是你得到错误的原因。
一种解决方案是在插入后使用get
查找值,以获得不可变借用。
pub fn get_or_insert<'a, S>(map: &'a mut Map, name: S) -> Handle<'a>
where S: Into<String>
{
let key = name.into();
map.entry(key.clone()).or_insert(Record::new(key.clone()));
let record = map.get(&key).unwrap();
Handle::new(record, map)
}
请注意,这仍然不允许您使用您提供的签名实施Handle::mut_record
; Handle
只有对地图和记录的不可变引用,并且您无法获得对记录的可变引用。