在包含引用

时间:2017-04-27 05:44:20

标签: hashmap rust lifetime borrow-checker

我正在使用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函数在记录中有一个可变函数,但不是真正的罪魁祸首。

我们现在想要实现一个函数,该函数返回RecordHashMap的引用,以便我们可以就地修改它。除此之外,我们希望能够控制返回的引用,以便我们可以做一些副作用(对于这个例子,我们假设我们想要打印出正在发生的事情就足够了,但它可能是一些处理统计和/或访问其他存储或进行延迟评估的其他操作)。为了解决这个问题,我们引入了一个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作为实现提供的抽象,抽象出对底层存储的访问。

  • 在编译时捕获误用而不是运行时,这会导致HandleRefCell取消资格。

  • 在基础结构中执行单个查找。

我查看了Rc但是将检查从编译时移到了运行时,能够在编译时捕获RefCell的误用是有益的。

Scope and shadowing中的问题与此问题类似,但答案是使用Handle,它解决了检查而不是解决问题。

对我来说,问题似乎是需要有一种方法将可变引用转换为不可变引用并释放可变引用(限制它应该被代码允许),但仍然不确定是否我误解了一些事情。

更新:此问题最初有三个子弹试图使其更具结构性,但它已被重写为只提出一个问题,以明确目标是什么是

1 个答案:

答案 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只有对地图和记录的不可变引用,并且您无法获得对记录的可变引用。