Currently HashMap
and HashSet
rely on the inherent implementations of the Eq
and Hash
traits. I propose moving this behaviour to the S
(RandomState
) parameter. This change is backwards compatible, but improves flexibility and makes it easier to implement custom comparisons.
The current approach as several downsides:
- Customizing comparisons requires implementing a new-type. This is often a clean way to model, it can also be unnecessarily complex. For example just because I want to compare strings case-insensitively, doesn't mean that it's a whole new kind of string.
- If you want to do a borrowed lookup using a dynamically sized type (e.g.
str
), you need to either implement yet another new-type around&str
, or unsafely implement wrapping ofstr
. - The comparison is determined at compile-time. Sometimes it's needed to customize comparisons at runtime, for example culture/collation dependent string comparisons.
Hasher
constrains hashing implementations to a particular iterative approach. This can hurt performance (I've seen several complaints about this, for example)Borrow
in the signatures of methods is rather confusing for beginners
As an alternative I'd introduce these traits:
pub trait EqPolicy<T: ?Sized, Q: ?Sized = T> {
fn eq(&self, x: &T, y: &Q) -> bool;
}
pub trait HashPolicy<T: ?Sized> {
fn hash(&self, x: &T) -> u64;
}
The methods of HashMap
should then express their constraints in terms of these traits:
impl<K, V, S> HashMap<K, V, S>
where
S: EqPolicy<K> + HashPolicy<K>,
{
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
where
S: EqPolicy<K, Q> + HashPolicy<Q>;
pub fn insert(&mut self, k: K, v: V) -> Option<V>;
}
This can be made backwards compatible by adding a blanket implementation based on the existing traits.
impl<S, T> HashPolicy<T> for S
where
S: BuildHasher,
T: Hash + ?Sized,
{
fn hash(&self, x: &T) -> u64 {
self.hash_one(x)
}
}
impl<S, K, Q> EqPolicy<K, Q> for S
where
S: BuildHasher,
K: Borrow<Q> + ?Sized,
Q: Eq + ?Sized,
{
fn eq(&self, x: &K, y: &Q) -> bool {
x.borrow() == y
}
}
Playground which contains a compilable example, and demonstrates that that the new constraints are compatible with the old constraints.
I'm not sure what the best name for these traits is. Alternatives could be EqComparer
and HashCalculator
. Hasher
is unfortunately already taken.
Similar changes could be done to BTreeMap
/BTreeSet
, but would require adding an additional generic parameter for the policy.