colinfang
colinfang

Reputation: 21757

How can I get a reference to the key and value immediately after inserting into a `HashMap`?

use std::collections::HashMap;
use std::collections::hash_map::Entry::*;

fn hook(k: &str, v: &str) {}

fn tt(k: String, v: String) -> Option<String> {
    let mut a: HashMap<String, String> = HashMap::new();
    match a.entry(k) {
        Occupied(mut occupied) => {
            let old = occupied.insert(v);
            //hook(&k, &old);
            Some(old)
        }
        Vacant(vacant) => {
            let v = vacant.insert(v);
            let k = vacant.key(); // Why doesn't it work?
            //hook(&k, v);
            None
        }
    }
}

I would like to call hook immediately after a key is inserted into the HashMap. It seems I have to use Entry. However, I am unable to call vacant.key() right after vacant.insert.

Upvotes: 9

Views: 2359

Answers (1)

Shepmaster
Shepmaster

Reputation: 431809

TL;DR: You cannot (right now?)


The compiler tells you why it doesn't work. Don't hesitate to read Rust compiler messages; they aren't scary and a lot of effort has gone into them!

error[E0382]: use of moved value: `vacant`
  --> src/main.rs:16:21
   |
15 |             let v = vacant.insert(v);
   |                     ------ value moved here
16 |             let k = vacant.key();
   |                     ^^^^^^ value used here after move
   |
   = note: move occurs because `vacant` has type `std::collections::hash_map::VacantEntry<'_, std::string::String, std::string::String>`, which does not implement the `Copy` trait

This is just normal Rust code. VacantEntry::insert consumes selfby value, returning a reference to the inserted value:

fn insert(self, value: V) -> &'a mut V

There are over 100 other question/answer pairs that ask about this error message, so I'll assume you've read enough of them to understand the problem; that's why we answer questions on Stack Overflow after all!


So why is it this way? That's tougher to answer. When you insert into a HashMap, it takes ownership of the key and the value. When you use the entry API, there's an intermediate step - you've given ownership of the key to the entry. The entry also has a mutable reference to the HashMap; a "bookmark" of where the value is.

When the value is missing and then you insert a value, the key is transferred out of the entry and into the HashMap. This means that the reference to the key inside the entry would be invalidated. That's why you can't reorder the two lines.

However, thinking about it a bit deeper, the value returned from insert refers to the underlying HashMap, after the value has been inserted. I can't see any reason preventing a function from being added that returns a reference to the key and the value. However, such a function doesn't exist now.

See also How can I keep a reference to a key after it has been inserted into a HashMap?


I'm pretty sure in this case you don't need the functionality though.

If you want to call it for the new value, just do all of this before the insertion:

hook(&k, &v);
a.insert(k, v);

If you want to do it only for the old value, doing nothing when there wasn't previously a value, you can:

Occupied(mut occupied) => {
    let old = occupied.insert(v);
    hook(occupied.key(), &old);
    Some(old)
}

If you want to call the hook with the old value if there was one and the new value if inserting (which seems inconsistent), you can call it before adding, the hook function will be none the wiser as the arguments are just references:

Vacant(vacant) => {
    hook(vacant.key(), &v);
    let v = vacant.insert(v);
    None
}

Upvotes: 4

Related Questions