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.)
If the
Cloneimplementation of aHashMap's key produces a key with a different hash, or ifkey.clone() == keydoes not hold, then this can result in a brokenHashMapaftermap.clone()(same forHashSet).For example, consider this struct with a custom
Cloneimplementation: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, whereHashis preserved, butx.clone() == xdoes not hold, which still leads to weird behaviour due to duplicate keys.The underlying cause seems to be that
HashMap::clone()relies onKey::clone()to preserve the key'sHash, placing the cloned value in the same bucket as the original one, and also assumes thatkey.clone() == key, not checking for duplicate keys after cloning. However, these assumptions don't seem to be documented anywhere:Clonedoes not requirex.clone() == x, and I couldn't find any notes on this in theHash,Eq,HashMaporHashSetdocumentation, or any discussions or issues on this topic at all.Being fairly new to Rust, I don't know whether
HashMapandHashSetshould be able to handle such weirdCloneimplementations, but I would at least expect these assumptions to be made explicit somewhere – perhaps as another "logic error" forHashMapandHashSet?(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 guaranteesHashSet::clone()can provide. Digging into the implementation, I realized thatRawTable::clone_from_impl()seems to silently assume that cloning a key preserves its hash and produces a key equal to the original one.)