如何 return 对 RefCell 内容的 *可选* 引用
How to return an *optional* reference into RefCell contents
我有一个类型将其数据存储在 Rc<RefCell<>>
后面的容器中,它大部分隐藏在 public 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)
}
// ...
}
但是,查看容器内部需要 returning 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
的非恐慌版本,即 return Option<Ref<'_, Value>>
。这是一个问题,因为 Ref::map
要求你 return 引用存在于 RefCell
中的东西,所以即使我想 return Ref<'_, Option<Value>>
,它不会工作,因为 storage.get()
编辑的选项 return 是短暂的。
尝试使用 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
}
}
可编译示例in the playground.
等相关问题假设内部引用始终可用,因此他们没有这个问题。
我找到了Ref::filter_map()
which would solve this,但它还没有稳定版,目前还不清楚它离稳定还有多远。除非有其他选择,否则我会接受使用 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 _)))
}
}
我简单地借用了 hashmap 两次,然后丢弃了生命周期(通过将引用转换为指针),然后通过重新借用指针的引用对象将其取回。我取消了生命周期参数只是为了确保它不会变得太长。
我认为是正确的。尽管如此,我还是会继续期待 filter_map
只是为了确定。
Asker 后来想出了这个变体,我把它放在这里是为了避免 link rot:
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 }))
}
您可以使用副作用来传达查找是否成功,然后 return 如果您没有成功的值,则 Ref::map
中的任意值。
impl Container {
// ...
fn peek(&self, key: u32) -> Option<Ref<'_, Value>> {
let storage = self.storage.borrow();
if storage.is_empty() {
// The trick below requires the map to be nonempty, but if it's
// empty, then we don't need to do a lookup.
return None;
}
// Find either the correct value or an arbitrary one, and use a mutable
// side channel to remember which one it is.
let mut failed = false;
let ref_maybe_bogus: Ref<'_, Value> = Ref::map(storage, |storage| {
storage.get(&key).unwrap_or_else(|| {
// Report that the lookup failed.
failed = true;
// Return an arbitrary Value which will be ignored.
// The is_empty() check above ensured that one will exist.
storage.values().next().unwrap()
})
});
// Return the ref only if it's due to a successful lookup.
if failed {
None
} else {
Some(ref_maybe_bogus)
}
}
}
改进:
如果 Value
类型可以有常量实例,那么您可以 return 其中之一而不是要求映射为非空;上面的方法只是适用于 Value
的任何定义的最通用方法,而不是最简单的方法。 (这是可能的,因为 &'static Value
满足 Ref
的要求——引用只需要足够长的时间,而不是实际指向 RefCell
的内容。)
如果 Value
类型可以有一个常量实例 ,它不同于映射 中的任何有意义的实例(“ sentinel value"),那么您可以在最终 if
中检查该值,而不是检查单独的布尔变量。但是,这并没有特别简化代码;如果你有一个用于其他目的的哨兵,或者如果你喜欢避免副作用的“纯函数式”编码风格,它最有用。
当然,如果 Ref::filter_map
变得稳定,这一切都没有实际意义。
这是我在 Ref::filter_map()
稳定之前最终使用的解决方案。它更改了问题中指定的 peek()
的签名,因此我不会接受这个答案,但它可能对偶然发现此问题的其他人有用。
虽然 peek()
是一个强大的原语,但它在调用站点的 用法 归结为检查值的某些属性并据此做出决策。对于那种用法,调用者不需要保留引用,它只需要临时访问它来提取它关心的属性。所以我们可以让 peek
接受一个检查值的闭包,并且 return 它的结果:
fn peek<F: FnOnce(&Value) -> R, R>(&self, key: u32, examine: F) -> Option<R> {
self.storage.borrow().get(&key).map(examine)
}
最初指定的 peek()
应该写成:
if let Some(event) = container.peek() {
if event.time() >= deadline {
container.remove_next();
}
}
...使用此答案中的 peek()
,人们会改为写:
if let Some(event_time) = container.peek(|e| e.time()) {
if event_time >= deadline {
container.remove_next();
}
}
我有一个类型将其数据存储在 Rc<RefCell<>>
后面的容器中,它大部分隐藏在 public 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)
}
// ...
}
但是,查看容器内部需要 returning 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
的非恐慌版本,即 return Option<Ref<'_, Value>>
。这是一个问题,因为 Ref::map
要求你 return 引用存在于 RefCell
中的东西,所以即使我想 return Ref<'_, Option<Value>>
,它不会工作,因为 storage.get()
编辑的选项 return 是短暂的。
尝试使用 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
}
}
可编译示例in the playground.
我找到了Ref::filter_map()
which would solve this,但它还没有稳定版,目前还不清楚它离稳定还有多远。除非有其他选择,否则我会接受使用 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 _)))
}
}
我简单地借用了 hashmap 两次,然后丢弃了生命周期(通过将引用转换为指针),然后通过重新借用指针的引用对象将其取回。我取消了生命周期参数只是为了确保它不会变得太长。
我认为是正确的。尽管如此,我还是会继续期待 filter_map
只是为了确定。
Asker 后来想出了这个变体,我把它放在这里是为了避免 link rot:
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 }))
}
您可以使用副作用来传达查找是否成功,然后 return 如果您没有成功的值,则 Ref::map
中的任意值。
impl Container {
// ...
fn peek(&self, key: u32) -> Option<Ref<'_, Value>> {
let storage = self.storage.borrow();
if storage.is_empty() {
// The trick below requires the map to be nonempty, but if it's
// empty, then we don't need to do a lookup.
return None;
}
// Find either the correct value or an arbitrary one, and use a mutable
// side channel to remember which one it is.
let mut failed = false;
let ref_maybe_bogus: Ref<'_, Value> = Ref::map(storage, |storage| {
storage.get(&key).unwrap_or_else(|| {
// Report that the lookup failed.
failed = true;
// Return an arbitrary Value which will be ignored.
// The is_empty() check above ensured that one will exist.
storage.values().next().unwrap()
})
});
// Return the ref only if it's due to a successful lookup.
if failed {
None
} else {
Some(ref_maybe_bogus)
}
}
}
改进:
如果
Value
类型可以有常量实例,那么您可以 return 其中之一而不是要求映射为非空;上面的方法只是适用于Value
的任何定义的最通用方法,而不是最简单的方法。 (这是可能的,因为&'static Value
满足Ref
的要求——引用只需要足够长的时间,而不是实际指向RefCell
的内容。)如果
Value
类型可以有一个常量实例 ,它不同于映射 中的任何有意义的实例(“ sentinel value"),那么您可以在最终if
中检查该值,而不是检查单独的布尔变量。但是,这并没有特别简化代码;如果你有一个用于其他目的的哨兵,或者如果你喜欢避免副作用的“纯函数式”编码风格,它最有用。
当然,如果 Ref::filter_map
变得稳定,这一切都没有实际意义。
这是我在 Ref::filter_map()
稳定之前最终使用的解决方案。它更改了问题中指定的 peek()
的签名,因此我不会接受这个答案,但它可能对偶然发现此问题的其他人有用。
虽然 peek()
是一个强大的原语,但它在调用站点的 用法 归结为检查值的某些属性并据此做出决策。对于那种用法,调用者不需要保留引用,它只需要临时访问它来提取它关心的属性。所以我们可以让 peek
接受一个检查值的闭包,并且 return 它的结果:
fn peek<F: FnOnce(&Value) -> R, R>(&self, key: u32, examine: F) -> Option<R> {
self.storage.borrow().get(&key).map(examine)
}
最初指定的 peek()
应该写成:
if let Some(event) = container.peek() {
if event.time() >= deadline {
container.remove_next();
}
}
...使用此答案中的 peek()
,人们会改为写:
if let Some(event_time) = container.peek(|e| e.time()) {
if event_time >= deadline {
container.remove_next();
}
}