【发布时间】:2017-04-27 05:44:19
【问题描述】:
我正在使用HashMap,但我对如何“释放”HashMap 的可变借用感到困惑,并且找不到如何做到这一点的好解释。
这只是一个示例,目标不是“解决问题”,而是了解如何完成此任务和/或为什么不应该这样做。
该示例由一个HashMap 组成,其中存储了一些简单的Records:
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 函数在记录中有一个可变函数,但这不是真正的罪魁祸首。
我们现在想要实现一个函数,该函数在HashMap 中返回对Record 的引用,以便我们可以就地修改它。除此之外,我们希望能够控制返回的引用,以便我们可以做一些副作用(对于这个例子,我们假设我们想要打印出正在发生的事情就足够了,但它可能是一些其他处理统计信息和/或访问其他存储或进行惰性评估的操作)。为了解决这个问题,我们引入了一个 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 的不可变借用,因此不应修改 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
HashMap 有两个引用,第一个是可变借用。我们需要先“释放”映射的第一个可变借用,然后才能获取不可变借用。我尝试以这种方式编写代码,并在第一个可变借用周围添加了一个作用域,期望它在作用域结束时被“释放”:
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)
}
但错误仍然存在。
很奇怪,即使在作用域完成后,可变借用仍然存在。根据References and Borrowing,借用应该在作用域的末尾结束,根据Scope and shadowing,作用域由块控制,块是用大括号括起来的语句的集合,所以从表面上看,后面的函数定义应该结束范围与对地图的引用的可变借用。
您如何以合理的方式实现这样的Handle,以使Handle 的生命周期不超过HashMap 的生命周期并在编译时捕获它?我正在寻找一种创建Handle 的好方法
通过使用临时
Handle作为实现提供的抽象来抽象出对底层存储的访问。在编译时而非运行时捕获误用,这将取消
RefCell和Rc的资格。在底层结构中执行单次查找。
我查看了RefCell,但这会将检查从编译时转移到运行时,如果能够在编译时发现Handle 的误用,将是有益的。
Rust: Borrowing issues with attempted caching 中的问题与此问题类似,但答案是使用UnsafeCell,它可以绕过检查而不是解决它们。
对我来说,问题似乎是需要有一种方法将可变引用转换为不可变引用并释放可变借用(具有代码应允许的限制),但仍不确定是否我误会了什么。
更新:这个问题最初有三个项目符号,以使其更有条理,但它已被重写为仅提出一个问题以明确目标是什么。 p>
【问题讨论】:
-
我不确定这里的最终目标。但是您应该能够创建一个包装器对象并将
get_mut()返回的值包装 -
嗯...这就是我想要做的(
Handler是包装对象)。问题再次是如何释放可变借用,一旦你用它来定位你想要包装的Record。 -
对不起,但我并不真正理解您引用的问题如何提供答案。可变借用被包裹在一个块中(返回对记录的可变引用),但它仍然没有结束可变借用(地图的)。
-
另外,这个例子很难再进一步拆分,因为问题实际上是关于生命周期的。真正的问题是最后一个问题,前两个只是提取问题的一部分,但我可以重写帖子,使其包含一个问题。
-
重写了正文和标题。看看你是否认为它更清楚。
标签: hashmap rust lifetime borrow-checker