-
Notifications
You must be signed in to change notification settings - Fork 320
Description
If the Clone
implementation of a HashMap
's key produces a key with a different hash, or if key.clone() == key
does not hold, then this can result in a broken HashMap
after map.clone()
(same for HashSet
).
For example, consider this struct with a custom Clone
implementation:
#[derive(PartialEq, Eq, Hash, Debug)]
struct FakeInt(u64);
impl Clone for FakeInt {
fn clone(&self) -> Self {
FakeInt { 0: 0 }
}
}
fn test_fake_int() {
let mut map = HashMap::new();
map.insert(FakeInt { 0: 1 }, 1);
map.insert(FakeInt { 0: 2 }, 2);
let c = map.clone();
println!("{:?}", c); // gives {FakeInt(0): 1, FakeInt(0): 2}
assert!(c.get(&FakeInt { 0: 0 }).is_none());
}
After cloning, FakeInt(0)
is in the map twice in a way, but at the same time cannot be queried or removed. This playground link contains the above example and an additional one, where Hash
is preserved, but x.clone() == x
does not hold, which still leads to weird behaviour due to duplicate keys.
The underlying cause seems to be that HashMap::clone()
relies on Key::clone()
to preserve the key's Hash
, placing the cloned value in the same bucket as the original one, and also assumes that key.clone() == key
, not checking for duplicate keys after cloning. However, these assumptions don't seem to be documented anywhere: Clone
does not require x.clone() == x
, and I couldn't find any notes on this in the Hash
, Eq
, HashMap
or HashSet
documentation, or any discussions or issues on this topic at all.
Being fairly new to Rust, I don't know whether HashMap
and HashSet
should be able to handle such weird Clone
implementations, but I would at least expect these assumptions to be made explicit somewhere – perhaps as another "logic error" for HashMap
and HashSet
?
(How I came across this issue: I wanted to write a specification for HashSet::clone()
with Verus, and for this tried to figure out which guarantees HashSet::clone()
can provide. Digging into the implementation, I realized that RawTable::clone_from_impl()
seems to silently assume that cloning a key preserves its hash and produces a key equal to the original one.)