rust - 如何将*可选*引用返回到 RefCell 内容
问题描述
我有一种将其数据存储在 a 后面的容器中的类型,该容器Rc<RefCell<>>
大部分对公共 API 隐藏。例如:
struct Value;
struct Container {
storage: Rc<RefCell<HashMap<u32, Value>>>,
}
impl Container {
fn insert(&mut self, key: u32, value: Value) {
self.storage.borrow_mut().insert(key, value);
}
fn remove(&mut self, key: u32) -> Option<Value> {
self.storage.borrow_mut().remove(&key)
}
// ...
}
但是,查看容器内部需要返回一个Ref
. 这可以使用Ref::map()
- 例如:
// peek value under key, panicking if not present
fn peek_assert(&self, key: u32) -> Ref<'_, Value> {
Ref::map(self.storage.borrow(), |storage| storage.get(&key).unwrap())
}
但是,我想要一个不惊慌失措的peek
, 会返回Option<Ref<'_, Value>>
. 这是一个问题,因为Ref::map
要求您返回对 内部存在的东西的引用RefCell
,所以即使我想 return Ref<'_, Option<Value>>
,它也不起作用,因为返回的选项storage.get()
是短暂的。
尝试使用从以前查找的密钥Ref::map
创建一个Ref
也不会编译:
// doesn't compile apparently the borrow checker doesn't understand that `v`
// won't outlive `_storage`.
fn peek(&self, key: u32) -> Option<Ref<'_, Value>> {
let storage = self.storage.borrow();
if let Some(v) = storage.get(&key) {
Some(Ref::map(storage, |_storage| v))
} else {
None
}
}
有效的方法是执行两次查找,但这是我真正想避免的事情:
// works, but does lookup 2x
fn peek(&self, key: u32) -> Option<Ref<'_, Value>> {
if self.storage.borrow().get(&key).is_some() {
Some(Ref::map(self.storage.borrow(), |storage| {
storage.get(&key).unwrap()
}))
} else {
None
}
}
操场上的可编译示例。
像这样的相关问题假设内部参考总是可用的,所以他们没有这个问题。
我发现Ref::filter_map()
哪个可以解决这个问题,但它还没有稳定版,而且还不清楚它离稳定还有多远。除非有其他选择,否则我会接受一个使用的解决方案,unsafe
只要它是健全的并且依赖于书面保证。
解决方案
我设法想出了这个:
fn peek<'a>(&'a self, key: u32) -> Option<Ref<'a, Value>> {
// Safety: we perform a guarded borrow, then an unguarded one.
// If the former is successful, so must be the latter.
// Conceptually, they are the same borrow: we just take the pointer
// from one and the dynamic lifetime guard from the other.
unsafe {
let s = self.storage.borrow();
let u = self.storage.try_borrow_unguarded().unwrap();
u.get(&key).map(|v| Ref::map(s, |_| &*(v as *const _)))
}
}
我只是借用了两次哈希图,然后抛弃了生命周期(通过将引用转换为指针),然后通过重新借用指针的所指对象将其恢复。我取消了生命周期参数,以确保它不会变得太长。
我认为这是正确的。尽管如此,我还是会继续期待以filter_map
确保。
Asker 后来想出了这个变体,为了避免链接失效,我将其包括在这里:
fn peek<'a>(&'a self, key: u32) -> Option<Ref<'a, Value>> {
// Safety: we convert the reference obtained from the guarded borrow
// into a pointer. Dropping the reference allows us to consume the
// original borrow guard and turn it into a new one (with the same
// lifetime) that refers to the value inside the hashmap.
let s = self.storage.borrow();
s.get(&key)
.map(|v| v as *const _)
.map(|v| Ref::map(s, |_| unsafe { &*v }))
}
推荐阅读
- javascript - Mongoose 查询 - groupBy 类别并获取每个类别的最后 4 项
- mysql - SQL 查询计数统计信息 - 计算这是否是特定用户表中的第一条记录
- r - 如何从ggplot中的饼图中删除白色边距
- airflow - AWS MWAA(托管 Apache Airflow)将 dags 中使用的 python 代码放在哪里?
- c++ - 在 C++ 中将一个大哈希拆分为较小的哈希
- mysql - 如何编写查询以使用表 A 和 C 中的数据更新表 B?有数百个 id 来连接两个表
- arrays - 在 Google Sheet 公式中的查询之前添加索引列
- json - 基于 Json 模式条件的属性值
- flutter - 将数据显示为 GridView 而不是 ListView
- python - 通过models.py进行简单计算的一个问题