`HashMap::get_mut` leading to "returns reference to local value", any efficient work-around?

Viewed 81

There have been a fair number of questions around this, and the solution is mostly "use Entry".

However this is an issue because HashMap::entry() requires an owned value meaning possibly expensive copies / allocations even when the key is already present and we just want to update the value in-place, hence the use of get_mut. However the use of get_mut on a reference to a local leads rustc to assume that said reference gets stored into the hashmap, and thus that returning the hashmap is an error:

use std::borrow::Cow;
use std::collections::HashMap;

fn get_string() -> String { String::from("xxxxxxx") }
fn foo() -> HashMap<Cow<'static, str>, usize> {
    let mut v = HashMap::new();

    // stand-in for "get a string slice as key",
    // real case is getting a String from an 
    // mpsc and the key being a segment of that string
    let s = get_string();
    // stand-in for a structure which contains an `Option<Cow>`
    let k = Cow::from(&s[2..3]);

    // because of get_mut, `&s` is apparently considered to be stored in `v`?
    if let Some(e) = v.get_mut(&k) {
        *e += 1;
    } else {
        v.insert(Cow::from(k.into_owned()), 0);
    }

    v
}

Note that the manipulations at lines 9~13 are there to clarify the point of the pattern, but get_mut alone is sufficient to trigger the issue

Is there a way around without the efficiency hit, or is an eager allocation the only way? (note: because this is a static issue, dynamic gates like contains_key or get obviously don't do anything).

1 Answers

According to the docs, HashSet::get_mut() requires a value of type &Q such that the key of the hash implements Borrow<Q>.

The key of your hash is Cow<'static, str>, that implements Borrow<str>. This means that you can use either a &Cow<'static, str> or a &str. But you are passing a &Cow<'local, str> for some 'local lifetime. The compiler tries to match that 'local with 'static and issues a somewhat confusing error message about lifetimes.

The solution is actually easy, because you can get an &str from the Cow either calling k.as_ref() or doing &*k, and the lifetime of the &str is unrestricted: (playground)

let k = Cow::from(&s[2..3]);
if let Some(e) = v.get_mut(k.as_ref()) { /* ...*/ }
Related