1use std::hash::{BuildHasher, Hasher};
33
34#[expect(
36 clippy::disallowed_types,
37 reason = "the aliases are the sanctioned spelling of std's map"
38)]
39pub type FastMap<K, V> = std::collections::HashMap<K, V, FastState>;
40
41#[expect(
43 clippy::disallowed_types,
44 reason = "the aliases are the sanctioned spelling of std's map"
45)]
46pub type FastSet<T> = std::collections::HashSet<T, FastState>;
47
48#[expect(
50 clippy::disallowed_types,
51 reason = "the aliases are the sanctioned spelling of std's map"
52)]
53pub type QualityMap<K, V> = std::collections::HashMap<K, V, QualityState>;
54
55#[expect(
57 clippy::disallowed_types,
58 reason = "the aliases are the sanctioned spelling of std's map"
59)]
60pub type QualitySet<T> = std::collections::HashSet<T, QualityState>;
61
62macro_rules! seeded_state {
63 ($(#[$state_doc:meta])* $state:ident, $(#[$hasher_doc:meta])* $hasher:ident, $inner:ident) => {
64 $(#[$state_doc])*
65 #[derive(Clone, Default)]
66 pub struct $state(foldhash::$inner::RandomState);
67
68 impl std::fmt::Debug for $state {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.debug_struct(stringify!($state)).finish_non_exhaustive()
72 }
73 }
74
75 impl BuildHasher for $state {
76 type Hasher = $hasher;
77
78 #[inline(always)]
79 fn build_hasher(&self) -> $hasher {
80 $hasher(self.0.build_hasher())
81 }
82 }
83
84 $(#[$hasher_doc])*
85 #[derive(Clone)]
86 pub struct $hasher(foldhash::$inner::FoldHasher<'static>);
87
88 impl std::fmt::Debug for $hasher {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 f.debug_struct(stringify!($hasher)).finish_non_exhaustive()
91 }
92 }
93
94 impl Hasher for $hasher {
95 #[inline(always)]
96 fn write(&mut self, bytes: &[u8]) {
97 self.0.write(bytes);
98 }
99
100 #[inline(always)]
101 fn write_u8(&mut self, i: u8) {
102 self.0.write_u8(i);
103 }
104
105 #[inline(always)]
106 fn write_u16(&mut self, i: u16) {
107 self.0.write_u16(i);
108 }
109
110 #[inline(always)]
111 fn write_u32(&mut self, i: u32) {
112 self.0.write_u32(i);
113 }
114
115 #[inline(always)]
116 fn write_u64(&mut self, i: u64) {
117 self.0.write_u64(i);
118 }
119
120 #[inline(always)]
121 fn write_u128(&mut self, i: u128) {
122 self.0.write_u128(i);
123 }
124
125 #[inline(always)]
126 fn write_usize(&mut self, i: usize) {
127 self.0.write_usize(i);
128 }
129
130 #[inline(always)]
131 fn finish(&self) -> u64 {
132 self.0.finish()
133 }
134 }
135 };
136}
137
138seeded_state!(
139 FastState,
141 FastHasher,
143 fast
144);
145
146seeded_state!(
147 QualityState,
150 QualityHasher,
152 quality
153);
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
162 fn fast_state_seeds_each_instance() {
163 let key = 0x5eed_u64;
164 assert_ne!(
165 FastState::default().hash_one(key),
166 FastState::default().hash_one(key)
167 );
168 }
169
170 #[test]
172 fn quality_state_seeds_each_instance() {
173 let key = 0x5eed_u64;
174 assert_ne!(
175 QualityState::default().hash_one(key),
176 QualityState::default().hash_one(key)
177 );
178 }
179
180 fn differs<S: BuildHasher>(state: &S, write: impl Fn(&mut S::Hasher, u8)) -> bool {
183 let hash = |v| {
184 let mut h = state.build_hasher();
185 write(&mut h, v);
186 h.finish()
187 };
188 hash(1) != hash(2)
189 }
190
191 #[test]
194 fn each_write_reaches_the_hash() {
195 fn check<S: BuildHasher>(state: &S) {
196 assert!(differs(state, |h, v| h.write(&[v; 3])), "write");
197 assert!(differs(state, |h, v| h.write_u8(v)), "write_u8");
198 assert!(differs(state, |h, v| h.write_u16(v.into())), "write_u16");
199 assert!(differs(state, |h, v| h.write_u32(v.into())), "write_u32");
200 assert!(differs(state, |h, v| h.write_u64(v.into())), "write_u64");
201 assert!(differs(state, |h, v| h.write_u128(v.into())), "write_u128");
202 assert!(
203 differs(state, |h, v| h.write_usize(v.into())),
204 "write_usize"
205 );
206 }
207 check(&FastState::default());
208 check(&QualityState::default());
209 }
210
211 #[test]
213 fn state_debug_hides_the_seed() {
214 assert_eq!(format!("{:?}", FastState::default()), "FastState { .. }");
215 assert_eq!(
216 format!("{:?}", QualityState::default()),
217 "QualityState { .. }"
218 );
219 }
220
221 #[test]
224 fn each_write_keeps_its_top_bit() {
225 fn check<S: BuildHasher>(s: &S) {
226 assert_ne!(s.hash_one(1_u8), s.hash_one(1_u8 | 1 << 7), "write_u8");
227 assert_ne!(s.hash_one(1_u16), s.hash_one(1_u16 | 1 << 15), "write_u16");
228 assert_ne!(s.hash_one(1_u32), s.hash_one(1_u32 | 1 << 31), "write_u32");
229 assert_ne!(s.hash_one(1_u64), s.hash_one(1_u64 | 1 << 63), "write_u64");
230 assert_ne!(
231 s.hash_one(1_u128),
232 s.hash_one(1_u128 | 1 << 127),
233 "write_u128"
234 );
235 let top = 1_usize << (usize::BITS - 1);
236 assert_ne!(
237 s.hash_one(1_usize),
238 s.hash_one(1_usize | top),
239 "write_usize"
240 );
241 }
242 check(&FastState::default());
243 check(&QualityState::default());
244 }
245
246 #[test]
248 fn each_state_wraps_its_foldhash_variant() {
249 let quality = std::any::type_name_of_val(&QualityState::default().0);
250 assert!(quality.starts_with("foldhash::quality::"), "{quality}");
251 let fast = std::any::type_name_of_val(&FastState::default().0);
252 assert!(fast.starts_with("foldhash::fast::"), "{fast}");
253 }
254}