如何将*可选*引用返回到 RefCell 内容中

use*_*342 8 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)
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

但是,查看容器内部需要返回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())
}
Run Code Online (Sandbox Code Playgroud)

但是,我想要一个非恐慌版本的peek,它会返回Option<Ref<'_, Value>>。这是一个问题,因为Ref::map要求您返回对 内存在的内容的引用RefCell,所以即使我想返回Ref<'_, Option<Value>>,它也不会工作,因为返回的选项storage.get()是短暂的。

尝试使用以前查找的密钥Ref::map创建 aRef也不会编译:

// 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
    }
}
Run Code Online (Sandbox Code Playgroud)

有效的方法是执行两次查找,但这是我真正想避免的:

// 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
    }
}
Run Code Online (Sandbox Code Playgroud)

操场上的可编译示例。

像这样的相关问题假设内部引用始终可用,所以他们不存在这个问题。

我找到了Ref::filter_map()可以解决这个问题的方法,但它还不能在稳定版上使用,而且还不清楚它离稳定版还有多远。除非有其他选择,我会接受一个解决方案,unsafe只要它是健全的并且依赖于书面保证。

Kev*_*eid 1

您可以使用副作用来传达查找是否成功,然后Ref::map如果没有成功的值,则返回任意值。

\n
impl Container {\n    // ...\n\n    fn peek(&self, key: u32) -> Option<Ref<\'_, Value>> {\n        let storage = self.storage.borrow();\n        if storage.is_empty() {\n            // The trick below requires the map to be nonempty, but if it\'s\n            // empty, then we don\'t need to do a lookup.\n            return None;\n        }\n\n        // Find either the correct value or an arbitrary one, and use a mutable\n        // side channel to remember which one it is.\n        let mut failed = false;\n        let ref_maybe_bogus: Ref<\'_, Value> = Ref::map(storage, |storage| {\n            storage.get(&key).unwrap_or_else(|| {\n                // Report that the lookup failed.\n                failed = true;\n                // Return an arbitrary Value which will be ignored.\n                // The is_empty() check above ensured that one will exist.\n                storage.values().next().unwrap()\n            })\n        });\n        \n        // Return the ref only if it\'s due to a successful lookup.\n        if failed {\n            None\n        } else {\n            Some(ref_maybe_bogus)\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

改进:

\n
    \n
  • 如果该Value类型可以有常量实例,那么您可以返回其中之一,而不要求映射非空;上面的方法只是适用于 的任何定义的最通用方法Value,而不是最简单的方法。(这是可能的,因为 a&\'static Value满足Ref\xe2\x80\x94 的要求,引用只需要生存足够长的时间,而不是实际指向RefCell\ 的内容。)

    \n
  • \n
  • 如果该Value类型可以具有一个与映射中找到的任何有意义的实例不同的常量实例(“哨兵值”),那么您可以在final中检查该值,if而不是检查单独的布尔变量。然而,这并没有特别简化代码;而是。如果您有一个用于其他目的的哨兵,或者您喜欢避免副作用的 \xe2\x80\x9cpure 函数式\xe2\x80\x9d 编码风格,那么它非常有用。

    \n
  • \n
\n

当然,如果Ref::filter_map变得稳定,这一切都毫无意义。

\n