I have a type that stores its data in a container behind a Rc<RefCell<>>
, which is for the most part hidden from the public API. For example:
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)
}
// ...
}
However, peeking inside the container requires returning a Ref
. That can be achieved using Ref::map()
- for example:
// 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())
}
However, I'd like to have a non-panicking version of peek
, that would return Option<Ref<'_, Value>>
. This is a problem because Ref::map
requires that you return a reference to something that exists inside the RefCell
, so even if I wanted to return Ref<'_, Option<Value>>
, it wouldn't work because the option returned by storage.get()
is ephemeral.
Trying to use Ref::map
to create a Ref
from a previously looked up key doesn't compile either:
// 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
}
}
The approach that does work is to perform the lookup twice, but that's something I'd really like to avoid:
// 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
}
}
Compilable example in the playground.
Related questions like this one assume that the inner reference is always available, so they don't have that issue.
I found Ref::filter_map()
which would solve this, but it's not yet available on stable, and it's unclear how far it is from stabilization. Barring other options, I would accept a solution that uses unsafe
provided it is sound and relies on documented guarantees.
You could use a side effect to communicate whether the lookup succeeded, then return an arbitrary value from
Ref::map
if you don't have a successful value.Refinements:
If the
Value
type can have constant instances, then you can return one of those instead of requiring the map to be nonempty; the method above is just the most general one that works for any definition ofValue
, and not the simplest. (This is possible since a&'static Value
satisfiesRef
's requirements — the reference just needs to live long enough, not to actually point into theRefCell
's contents.)If the
Value
type can have a constant instance that is distinct from any meaningful instance that would be found in the map (a "sentinel value"), then you can check for that value in the finalif
instead of checking a separate boolean variable. However, this doesn't especially simplify the code; it's mostly useful if you have a sentinel you're using for other purposes anyway, or if you like a “pure functional” coding style which avoids side effects.And of course, this is all moot if
Ref::filter_map
becomes stable.