首页 文章

在包含引用的临时值上执行生存期

提问于
浏览
1

我正在使用HashMap,但我在如何"release"对 HashMap 的可变借用方面磕磕绊绊,并且找不到如何做到这一点的好解释 .

这只是一个例子,目标不是“解决问题”,而是要了解如何实现这一目标和/或为什么不应该这样做 .

该示例包含一个 HashMap 存储一些简单的 Record

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 ,第一个是可变借用 . 在获取不可变借入之前,我们需要"release"对 Map 进行第一次可变借用 . 我尝试以这种方式编写代码,并在范围结束时在第一个可变扩展的借用中添加一个范围,期望它为"released":

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作用域由块控制,这些块是括在括号中的语句的集合,所以表面上看,后面的函数定义应该以可变的方式结束作用域 . 借用 Map 的参考 .

如何以合理的方式实现这样的 Handle ,以便 Handle 的生命周期不超过 HashMap 的生命周期并在编译时捕获它?我正在寻找一种创建 Handle 的好方法

  • 使用临时 Handle 作为实现提供的抽象,抽象出对底层存储的访问 .

  • 捕获在编译时而不是运行时的错误,这会使 RefCellRc 失去资格 .

  • 在底层结构中执行单个查找 .

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

Rust: Borrowing issues with attempted caching中的问题与此类似,但答案是使用 UnsafeCell ,它可以解决检查而不是解决问题 .

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

Update: 这个问题最初有三个子弹试图使其更加结构化,但它被重写为只提出一个问题,以明确目标是什么 .

1 回答

  • 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 只对 Map 和记录具有不可变引用,并且您无法获得对具有这些引用的记录的可变引用 .

相关问题