Ref::map and avoiding double HashMap lookup

Beginner question here. I thought I wasn't a beginner, but I guess I still am!

Let's say I want to look up something in a RefCell<HashMap> and return an Option<Ref> of the value.

fn do_the_lookup<'a>(
    map: &'a RefCell<HashMap<String, String>>,
    key: &str,
) -> Option<Ref<'a, String>> {
    let guard = map.borrow();
    if !guard.contains_key(key) {
        return None;
    }
    Some(Ref::map(guard, |map| map.get(key).unwrap()))
}

It works fine, except we're looking up the key twice (once in contains_key, once in get). Can we get rid of the doubled-up work?

fn do_the_lookup_only_once<'a>(
    map: &'a RefCell<HashMap<String, String>>,
    key: &str,
) -> Option<Ref<'a, String>> {
    let guard = map.borrow();
    let result = guard.get(key)?;
    Some(Ref::map(guard, |_| result))
}

The borrow checker doesn't like this. At the point where we move guard into Ref::map, the reference lifetime ends, so we have to throw away any work from before that point. And I can't know whether that call is needed (as opposed to returning None) without doing the lookup beforehand, which we'll then have to throw away and re-do. Is there a way around this?

Generally, you have to do the .get() call inside Ref::map() for this to work. The complication is that the closure in Ref::map() is expected to return a &U unconditionally, not an Option<&U>.

On nightly, you could use Ref::filter_map():

#![feature(cell_filter_map)]

fn do_the_lookup_only_once<'a>(
    map: &'a RefCell<HashMap<String, String>>,
    key: &str,
) -> Option<Ref<'a, String>> {
    let guard = map.borrow();
    Ref::filter_map(guard, |m| m.get(key)).ok()
}

One trick you could use to get this working on stable is using a dummy value:

fn do_the_lookup_only_once<'a>(
    map: &'a RefCell<HashMap<String, String>>,
    key: &str,
) -> Option<Ref<'a, String>> {
    let guard = map.borrow();
    static DUMMY: String = String::new();
    let r = Ref::map(guard, |m| m.get(key).unwrap_or(&DUMMY));
    if (&*r as *const String) == (&DUMMY as *const String) {
        None
    } else {
        Some(r)
    }
}

Playground.