Skip to main content

kovan_map/hopscotch/
std_traits.rs

1//! The `std::collections::HashMap`-shaped trait set: `Default`, `Debug`, `Clone`, `Extend`,
2//! `PartialEq` and `Eq`, each with std's own bounds where an honest concurrent implementation can
3//! give them, built entirely on `HopscotchMap`'s public API (`iter`, `get`, `len`,
4//! `with_capacity_and_hasher`, `insert`). None of it touches a write path's own body.
5//!
6//! `Clone` and `Extend<(K, V)>` also ask `K: Send, V: Send`, as `FromIterator` does: an entry
7//! their inserts replace is retired, and kovan may run a retired entry's destructor on another
8//! thread (the contract of `kovan::retire`), which a `!Send` key or value must never meet.
9//! `Extend<(&K, &V)>` needs no `Send`: its `Copy` keys and values have no destructor.
10//!
11//! `FromIterator` and both `IntoIterator` impls live in `iter.rs`, next to the iterator types
12//! they return; `Send`/`Sync`/`Drop` stay in the main module, next to the field layout they
13//! reason about.
14
15use super::HopscotchMap;
16use core::fmt;
17use core::hash::{BuildHasher, Hash};
18
19/// Creates an empty map with `S`'s default hasher and the default capacity, bounded by exactly
20/// what `std::collections::HashMap`'s `Default` needs: `S: Default` (construction never hashes,
21/// so `K`/`V` carry only the struct's own `'static`, and `S` needs no `BuildHasher` either). Not
22/// `#[cfg(feature = "std")]`: unlike [`HopscotchMap::new`] (which needs `foldhash::fast::FixedState`'s
23/// `std`-only default), this is generic over the caller's own `S`.
24impl<K: 'static, V: 'static, S: Default> Default for HopscotchMap<K, V, S> {
25    fn default() -> Self {
26        Self::with_hasher(S::default())
27    }
28}
29
30/// A snapshot of the map's entries, as `std::collections::HashMap`'s `Debug` prints its own.
31/// Bounded by what the walk needs (owned clones, keys compared to skip one met twice), never by
32/// `Hash` or `S: BuildHasher`: printing hashes nothing.
33impl<K, V, S> fmt::Debug for HopscotchMap<K, V, S>
34where
35    K: Eq + Clone + fmt::Debug + 'static,
36    V: Clone + fmt::Debug + 'static,
37{
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        f.debug_map().entries(self.iter()).finish()
40    }
41}
42
43/// A snapshot copy: a new map with the same capacity and a clone of the hasher, populated by
44/// inserting a clone of every entry this map held at some point during the walk. Like `iter`, a
45/// concurrent write to `self` during the clone may or may not be reflected in the result.
46impl<K, V, S> Clone for HopscotchMap<K, V, S>
47where
48    K: Hash + Eq + Clone + Send + 'static,
49    V: Clone + Send + 'static,
50    S: BuildHasher + Clone,
51{
52    fn clone(&self) -> Self {
53        let cloned = Self::with_capacity_and_hasher(self.capacity(), self.hasher().clone());
54        for (k, v) in self.iter() {
55            cloned.insert(k, v);
56        }
57        cloned
58    }
59}
60
61/// Inserts every pair, through the same write path [`HopscotchMap::extend`] (`&self`) uses: a
62/// `&mut self` receiver only because [`Extend`] requires one, never taken as exclusive access to
63/// the map's own writers.
64impl<K, V, S> Extend<(K, V)> for HopscotchMap<K, V, S>
65where
66    K: Hash + Eq + Clone + Send + 'static,
67    V: Clone + Send + 'static,
68    S: BuildHasher,
69{
70    fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
71        HopscotchMap::extend(&*self, iter);
72    }
73}
74
75/// As `std::collections::HashMap`'s `Extend<(&K, &V)>`: `K` and `V` copied out of the borrowed
76/// pairs, then inserted the same way as the owned-pair impl above.
77impl<'a, K, V, S> Extend<(&'a K, &'a V)> for HopscotchMap<K, V, S>
78where
79    K: Hash + Eq + Copy + Send + 'static,
80    V: Copy + Send + 'static,
81    S: BuildHasher,
82{
83    fn extend<I: IntoIterator<Item = (&'a K, &'a V)>>(&mut self, iter: I) {
84        HopscotchMap::extend(&*self, iter.into_iter().map(|(k, v)| (*k, *v)));
85    }
86}
87
88/// Content equality of a snapshot of each map: the same length and, for every pair this map's
89/// walk found, the other map holding that key with an equal value. Exact at quiescence and
90/// best-effort under a concurrent write to either map, the same honesty [`HopscotchMap::len`]
91/// documents; a lock-free map cannot hold both maps still for the comparison the way an
92/// exclusively-borrowed `std::collections::HashMap` can.
93impl<K, V, S> PartialEq for HopscotchMap<K, V, S>
94where
95    K: Hash + Eq + Clone + Send + 'static,
96    V: Clone + PartialEq + Send + 'static,
97    S: BuildHasher,
98{
99    fn eq(&self, other: &Self) -> bool {
100        self.len() == other.len()
101            && self
102                .iter()
103                .all(|(k, v)| other.get(&k).is_some_and(|ov| ov == v))
104    }
105}
106
107/// `V: Eq` makes the snapshot comparison above reflexive, symmetric and transitive at
108/// quiescence, as `std::collections::HashMap`'s `Eq` is.
109impl<K, V, S> Eq for HopscotchMap<K, V, S>
110where
111    K: Hash + Eq + Clone + Send + 'static,
112    V: Clone + Eq + Send + 'static,
113    S: BuildHasher,
114{
115}