Search code examples
rusthashmapborrow-checker

Cannot use the entry API to mutate a HashMap using a reference as the key inside of a function


I'm trying to get a handle to an element in a mutable HashMap reference where the keys are &str.

In the example below, I'm trying to get value dict[key] so I can mutate it. How do I do this?

I've tried:

  • dict.entry(key): lifetime mismatch
  • dict.entry(&String::from(key)): borrowed value does not live long enough

e.g. this:

use std::collections::HashMap;

fn do_thing(key: &str, dict: &mut HashMap<&str, u32>) -> u32 {
    let num = dict.entry(&String::from(key)).or_insert(0);
    *num += 1;
    return 42;
}

Errors out with:

error[E0716]: temporary value dropped while borrowed
 --> src/lib.rs:4:27
  |
3 | fn do_thing(key: &str, dict: &mut HashMap<&str, u32>) -> u32 {
  |                                           - let's call the lifetime of this reference `'1`
4 |     let num = dict.entry(&String::from(key)).or_insert(0);
  |               ------------^^^^^^^^^^^^^^^^^-             - temporary value is freed at the end of this statement
  |               |           |
  |               |           creates a temporary which is freed while still in use
  |               argument requires that borrow lasts for `'1`

Solution

  • Link the lifetime of the key argument to the lifetime of the keys in the HashMap:

    use std::collections::HashMap;
    
    fn do_thing<'a>(key: &'a str, dict: &mut HashMap<&'a str, u32>) -> u32 {
        *dict.entry(key).or_insert(0) += 1;
        42
    }
    

    dict.entry(key)

    The error message for this version helps understand the problem:

    use std::collections::HashMap;
    
    fn do_thing(key: &str, dict: &mut HashMap<&str, u32>) -> u32 {
        *dict.entry(key).or_insert(0) += 1;
        42
    }
    
    error[E0623]: lifetime mismatch
     --> src/lib.rs:4:17
      |
    3 | fn do_thing(key: &str, dict: &mut HashMap<&str, u32>) -> u32 {
      |                  ----                     ----
      |                  |
      |                  these two types are declared with different lifetimes...
    4 |     *dict.entry(key).or_insert(0) += 1;
      |                 ^^^ ...but data from `key` flows into `dict` here
    

    Specifically, entry will store key in the HashMap, but the value referenced by key might become invalid before the HashMap does. If that happened, the HashMap would contain a dangling reference, pointing to invalid memory. That's exactly what Rust's borrow checker prevents.

    See also:

    dict.entry(&String::from(key))

    This can never work here, for much the same reason.

    See also: