Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking issue for HashMap OccupiedEntry::{replace_key, replace_entry} #44286

Open
2 tasks done
Binero opened this issue Sep 3, 2017 · 19 comments
Open
2 tasks done

Tracking issue for HashMap OccupiedEntry::{replace_key, replace_entry} #44286

Binero opened this issue Sep 3, 2017 · 19 comments
Labels
A-collections Area: std::collections. B-unstable Feature: Implemented in the nightly compiler and unstable. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. Libs-Small Libs issues that are considered "small" or self-contained Libs-Tracked Libs issues that are tracked on the team's project board. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@Binero
Copy link
Contributor

Binero commented Sep 3, 2017

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.

@Binero Binero changed the title Allow to replace a HashMap and BTreeMap entry to be replaced. Allow a HashMap and BTreeMap entry to be replaced. Sep 3, 2017
@Mark-Simulacrum Mark-Simulacrum added C-feature-request Category: A feature request, i.e: not implemented / a PR. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Sep 5, 2017
@stepancheg
Copy link
Contributor

stepancheg commented Oct 6, 2017

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.

@Binero
Copy link
Contributor Author

Binero commented Oct 6, 2017

  • 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.

@stepancheg
Copy link
Contributor

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.

@Binero
Copy link
Contributor Author

Binero commented Oct 9, 2017

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

@Binero
Copy link
Contributor Author

Binero commented Oct 9, 2017

@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.

Binero added a commit to Binero/rust that referenced this issue Nov 11, 2017
This commit renames the `replace` function to `replace_entry`, and
creates a seperate `replace_key` function for `OccupiedEntry`. The
original `replace` function did not solve the use-case where the
key needed to be replaced, but not the value. Documentation and
naming has also been updated to better reflect what the original
replace function does.
bors added a commit that referenced this issue Nov 11, 2017
Addressed issues raised in #44286. (`OccupiedEntry::replace_entry`)

This commit renames the `replace` function to `replace_entry`, and
creates a seperate `replace_key` function for `OccupiedEntry`. The
original `replace` function did not solve the use-case where the
key needed to be replaced, but not the value. Documentation and
naming has also been updated to better reflect what the original
replace function does.
@dtolnay dtolnay added C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. and removed C-feature-request Category: A feature request, i.e: not implemented / a PR. labels Nov 18, 2017
@SimonSapin SimonSapin added the B-unstable Feature: Implemented in the nightly compiler and unstable. label Mar 17, 2018
@SimonSapin
Copy link
Contributor

This has been in Nightly for long enough that I’m inclined to stabilize, but I don’t 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?

@Binero
Copy link
Contributor Author

Binero commented Mar 17, 2018

@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.

@joshlf
Copy link
Contributor

joshlf commented Feb 5, 2020

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.

@jonas-schievink jonas-schievink added the A-collections Area: std::collections. label Mar 29, 2020
@jonas-schievink jonas-schievink changed the title Allow a HashMap and BTreeMap entry to be replaced. Tracking issue for HashMap OccupiedEntry::{replace_key, replace_entry} Mar 29, 2020
@jonhoo
Copy link
Contributor

jonhoo commented Jul 2, 2020

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.

@Amanieu
Copy link
Member

Amanieu commented Jul 2, 2020

That is correct.

@jonhoo
Copy link
Contributor

jonhoo commented Jul 2, 2020

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!

@KodrAus
Copy link
Contributor

KodrAus commented Jul 29, 2020

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: #65225 (comment)

@glaebhoerl
Copy link
Contributor

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.

@KodrAus KodrAus added Libs-Small Libs issues that are considered "small" or self-contained Libs-Tracked Libs issues that are tracked on the team's project board. labels Jul 29, 2020
@joshlf
Copy link
Contributor

joshlf commented Feb 11, 2021

Would somebody mind providing a short status update on what's left before stabilization?

@qm3ster
Copy link
Contributor

qm3ster commented May 10, 2021

Maybe it should at least return (K, &mut V), since it will consume the entry? (Which it has to, in order to return the key.)

My ideal API would be:

impl OccupiedEntry {
    fn swap_keys(&mut self);
    // +
    fn into_key(self) -> K;
}

It allows the most flexibility and I don't see an immediate drawback.
e.replace_key() is then just e.swap_keys(); e.into_key()
The latter can also be substituted for

fn into_inner(self) -> (K, &mut V);

Which is a little less ergonomic since we could already get that &mut with the HashMap's lifetime.
Edit: Ah, only into_mut(self) gives us the HashMap's lifetime, so maybe this is valuable.

Basically, I really want my K back :)

@cuviper
Copy link
Member

cuviper commented Feb 16, 2022

These methods will currently unwrap-panic if the OccupiedEntry was created by VacantEntry::insert_entry or a vacant Entry::insert_entry, both of which will be stable in Rust 1.59. In this case we no longer have a key because it was used for the new insertion.

edit: that's #65225, as mentioned already in #44286 (comment)

@qm3ster
Copy link
Contributor

qm3ster commented Feb 17, 2022

This is actually really wild.
It looks like OccupiedEntry.key has always been an Option<K>, but it was always constructed as a Some(key) and unwraped everywhere.
Was entry_insert planned from the initial commit of hashbrown?

@Amanieu
Copy link
Member

Amanieu commented Feb 17, 2022

I believe that part of hashbrown was just copied from the standard library when it was first implemented.

@eaglgenes101
Copy link

eaglgenes101 commented Feb 17, 2022

The entry_insert feature, as implemented and heading towards stabilization, clashes with this feature. (Specifically, using the entry replacement API after insert_entry causes a panic, since the entry replacement API relies on the entry key being present, while insert_entry will remove it.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-collections Area: std::collections. B-unstable Feature: Implemented in the nightly compiler and unstable. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. Libs-Small Libs issues that are considered "small" or self-contained Libs-Tracked Libs issues that are tracked on the team's project board. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests