2

I'm trying to efficiently look up or insert an item into a HashMap that owns both its keys and its values.

According to How to lookup from and insert into a HashMap efficiently?, the Entry API is the way to do this.

However, when inserting a new item, I also need access to the key.

Since HashMap::entry consumes the key, I cannot do the following, which fails with error[E0382]: borrow of moved value: 'index':

let mut map: HashMap<String, Schema> = ...;
let index: String = ...;

let schema = map
    .entry(index)
    .or_insert_with(|| Schema::new(&index));

The simplest way to get this working seems to be as follows:

let schema = match map.entry(index) {
    Entry::Occupied(e) => e.into_mut(),
    Entry::Vacant(e) => {
        // VacantEntry::insert consumes `self`,
        // so we need to clone:
        let index = e.key().to_owned();
        e.insert(Schema::new(&index))
    }
};

If only or_insert_with would pass the Entry to the closure that it calls, it would have been possible to write the above code like this:

let schema = map
    .entry(index)
    .or_insert_with_entry(|e| {
        let index = e.key().to_owned();
        Schema::new(&index)
    });

Did I overlook something? What would be the best way to write this code?

gavrie
  • 1,641
  • 1
  • 15
  • 14
  • It looks like your question might be answered by the answers of [How can I keep a reference to a key after it has been inserted into a HashMap?](https://stackoverflow.com/q/32401857/155423); [How can I get a reference to the key and value immediately after inserting into a `HashMap`?](https://stackoverflow.com/q/43681409); [How do I use the Entry API with an expensive key that is only constructed if the Entry is Vacant?](https://stackoverflow.com/q/51542024/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Sep 10 '19 at 13:24
  • 2
    @Shepmaster thanks. My question is indeed answered by https://stackoverflow.com/questions/51542024/ and https://stackoverflow.com/questions/43681409/, from which I gather that there is no better way with current stable Rust, but this may change in the future. – gavrie Sep 10 '19 at 13:39
  • 1
    Your third (hypothetical) snippet seems no better than the second (working) one -- both call `to_owned` on the string, apparently unnecessarily. Does `e.insert(Schema::new(e.key()))` not work? If that falls foul of the borrow checker, what about `let value = Schema::new(e.key()); e.insert(value)`? It depends on what `Schema::new` does, I suppose, but if it takes a `&str` you shouldn't need to make a `String` to call it. – trent Sep 10 '19 at 13:41
  • @trentcl I agree; it's just a bit more readable. Borrowing the key won't work in any case, since `e.insert` consumes `self` as I mentioned in the code comment. – gavrie Sep 10 '19 at 13:42
  • (And consequently, if you're cloning the string anyway, you might as well just call `entry(index.clone())` and then `or_insert_with` works fine as in the first snippet.) – trent Sep 10 '19 at 13:43
  • @trentcl the point is to clone only when needed (in case the entry doesn't exist), and avoid it in the common path. – gavrie Sep 10 '19 at 13:44
  • Yes, you have to pass an owned `String` to `entry`, so you would have to clone it if you wanted to use `index` again, but the snippets don't show that. You *don't* have to pass an owned `String` to `Schema::new`, so I don't understand why you *also* call `to_owned()` on `e.key()` in the working example. If you wanted to use `index` again afterwards, you would have to clone it a second time, which is why calling `entry(index.clone())` is no worse in that case. – trent Sep 10 '19 at 13:51

0 Answers0