Rust: Tracking issue for HashMap `OccupiedEntry::{replace_key, replace_entry}`

Created on 3 Sep 2017  路  13Comments  路  Source: rust-lang/rust

At the moment the only way to replace a map entry with a new one, swallows the old key. In cases the user wants to keep the old key, the only way to do so is to first remove_entry and to then insert a new one. This requires two map look-ups.

As the Entry API aims to make operations that would otherwise require two look-ups into operations that require only one, the replacing of an entry is an obvious hole in the API.

  • [x] Implemented in #44278
  • [x] Tweaked in #45152
A-collections B-unstable C-tracking-issue Libs-Small Libs-Tracked T-libs

Most helpful comment

Not sure if this was the original intent, but I have a use case for replace_key where I need the key back (by value) in certain failure conditions. I have code like:

match map.entry(key) {
    Entry::Occupied(entry) => 
        entry.into_mut().do_thing_which_can_fail().map_err(|_| entry.replace_key()),
    Entry::Vacant(entry) => ...,
}

Without replace_key, I'd need to do map.entry(key.clone()) instead so that I could still have key around by value.

All 13 comments

While it is indeed a missing API, there are several issues with this new API implemented in PR #44278:

  • it seems to panics if take_key was called before (it is not public API)
  • it replaces both key and value, so

    • is not possible to replace only key or only value (there's insert function which replaces only value)


    • it does do completely independent things in one operation

  • it is not possible to replace key in VacantEntry (it is pointless)
  • it takes self, while it could take &mut self, so replace could be called again if it is needed for some reason
  • key replacement it is against least surprise principle

I think HashMap::replace should be different:

impl OccupiedEntry<...> {
    fn replace_value(&mut self, new_value: V) -> V {
        replace value with new value, do not touch the key.
    }
}

And probably that's it.

If we need to replace key for some reason, replace_key operation needs to be added to both OccupiedEntry and VacantEntry.

  • take_key cannot be called before, all functions that call it consume the entry.
  • The point is to be able to swap out the key without multiple lookups. replace_value beats the point.
  • A VacantEntry does not have a key, so it's unclear how the function would work.

take_key cannot be called before, all functions that call it consume the entry.

Oh, it is not public API, thanks.

The point is to be able to swap out the key without multiple lookups. replace_value beats the point.

But this function does not allow to replace key without replacing value.

BTW, there's already a function to replace value, it's called insert.

So if we had replace_key instead of current replace, then current replace could be emulated as:

occupied_entry.insert(new_value);
occupied_entry.replace_key()

replace_key is not possible now, and replace is redundant if we had replace_key.

A VacantEntry does not have a key, so it's unclear how the function would work.

Please ignore that.

I will take a look at replace_key when I have time, probably next week.

@stepancheg I think it's probably better to have replace_key consume the entry, because the semantics of the function would otherwise change after the first call.

This has been in Nightly for long enough that I鈥檓 inclined to stabilize, but I don鈥檛 quite understand the use case. @Binero, can you explain some more why it can be important to recover the old key rather than the new one (passed to HashMap::entry() which hashes and compares equal?

@SimonSapin

It is quite a niche function, so it's certainly not instantly obvious where this would be useful.

That said, it has some uses when you want to for example lower the memory footprint of a HashMap by swapping out an Rc<T> with another, equivalent, Rc<T> that you are already using elsewhere.

If either T or the HashMap is large enough, this could reduce the memory footprint of the application by quite a lot.

Not sure if this was the original intent, but I have a use case for replace_key where I need the key back (by value) in certain failure conditions. I have code like:

match map.entry(key) {
    Entry::Occupied(entry) => 
        entry.into_mut().do_thing_which_can_fail().map_err(|_| entry.replace_key()),
    Entry::Vacant(entry) => ...,
}

Without replace_key, I'd need to do map.entry(key.clone()) instead so that I could still have key around by value.

I may be off-base here, but I feel like there is a requirement for these methods that the key continue to have the same hash? If so, that should almost certainly be stated in the documentation.

That is correct.

Ah, reading it again, I guess the reason this is okay is because you can only get an OccupiedEntry in the first place if the provided key already hashes to the appropriate value. Not mentioning it in the docs seems fine then!

It's not super easy to spot from this issue alone, but the replace_key method is incompatible with another unstable Entry::insert method. See: https://github.com/rust-lang/rust/issues/65225#issuecomment-571584668

I just happened upon a use case for this feature while generalizing the code linked here to arbitrary key types. Basically, it's a hash table with support for scoping and shadowing, and if a newly inserted key shadows an existing one, the old key and value are put into a separate Vec, and when the shadowing scope is popped, they are inserted back into the map. But insert() only gets one copy of the key as input, and that key can't simultaneously exist in two places: in the HashMap with the newly inserted value, and in the Vec alongside the previous/shadowed one. So in the case where the key already exists in the map, I need to be able to get the old value and the old key back out so I can put both of them into the Vec.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

withoutboats picture withoutboats  路  213Comments

nikomatsakis picture nikomatsakis  路  412Comments

nikomatsakis picture nikomatsakis  路  210Comments

Leo1003 picture Leo1003  路  898Comments

GuillaumeGomez picture GuillaumeGomez  路  300Comments