Hi, is anyone familiar as to why there's no single method for converting from an occupied entry to a vacant one during the removal operation? Would it be by any chance unsound to perform occupied.remove_and_vacate() which would return the original value along with the VacantEntry?
My question arose while I was trying "update or insert" using the entry api.
Note, I don't do this for performance reasons. Its the no-error-path behaviour that matters for me.
my guess is that it's just not something that generally useful, if you want the vacant entry you likely want to fill it in which case you can most likely just use insert and get the old value from the return.
unless you want to do something like fn change(T)->T on the value in which case i think that really would be better adressed at a language level by allowing to perform that kind of operation over &mut T
In general, temporarily moving out of a &mut T is not necessarily sound; if fn change(T) -> T can panic and panic=unwind, you could easily get a double-free (among other problems). Unwinding due to a panic would drop the T owned in change, but whatever the &mut T was referencing could also be dropped later during unwinding (or used with catch_unwind).
However, there's already sufficient std/core support for moving out of a &mut T and putting a temporary value in its place to prevent soundness issues. E.g., mem::replace, mem::take. Though maybe those functions don't address problems with the entry API? I don't know what I don't know.
What did you end up with and what do you wish it looked like instead? (What I imagine you want is possible with the entry API, albeit not with a single call necessarily.)
Hashbrown's OccupiedEntry::remove does give you a VacantEntry too:
We can't make a breaking change to match that in the standard library, but it could be a new method. If you'd like to propose that, see the ACP process here:
In the implementation, I notice that RustcOccupiedEntry (used by the standard library) does not have a hash field like hashbrown's own OccupiedEntry, which is needed to make a VacantEntry. Maybe that could be added, but changing the entry size in std is likely to have broad performance effects.
I'm doing an in-memory store based on few hash tables. I'd like to have strong guarantees that these tables are synced at all times. Thus once I obtain a Vacant entry from the main table all the auxiliary tables should also have entry for that key be vacant. This lets me better express invariants in type signatures of get / update methods on my store, prevents panics and lets me catch any potential bugs and log them.
My question arose when I was trying to handle a situation when the main table returns Vacant, but aux table would return Occupied (this implies a bug). In such case I'd like to remove whatever was previously there, log it, and return the now vacant entry.
What i'd expect instead would be something like this:
let now_vacant: VacantEntry<'_, _, _> = match self.computational_models.entry(id) {
Entry::Occupied(occupied) => occupied.vacate(),
Entry::Vacant(vacant) => vacant,
};
This could be altogether expressed as a method Entry::or_vacate (?) which would perform that action exactly with maybe additional return of the Option<V> if entry was occupied.
Essentially theres no way to express such a type-state'ish transition which seems to me should be perfectly sound.
Oh right, missing hash is a problem indeed. I'd have to look into this, at the first glance it seems that VacantEntry is anyway larger then the Occupied one, so maybe the size increase would not have any overall effect on the Entry as a whole?
EDIT: unfortunately its the hashbrown's HashTable which gives back the VacantEntry not the HashMap. Going from std's hashmap to hashbrown's was indeed a drop-in replacement, not so much unfortunately with changing to HashTable.
Oh! Sorry, I didn't notice I had strayed into the wrong docs there.
This isn't as direct, but replace_entry_with returns a full Entry. So if you return None in that closure, you can match the Entry::Vacant that comes out.