1#![allow(clippy::inline_always)]
5
6#[expect(clippy::disallowed_types)]
7use std::collections::{HashMap, HashSet};
8use std::hash::{BuildHasherDefault, Hasher};
9
10const SEED: u64 = 0xa076_1d64_78bd_642f;
12const MUL: u64 = 0xe703_7ed1_a0b4_28db;
14
15#[derive(Clone, Debug)]
56pub struct FastHasher {
57 state: u64,
58 pending: u64,
59 bits: u32,
60}
61
62impl Default for FastHasher {
63 #[inline(always)]
64 fn default() -> Self {
65 FastHasher {
66 state: SEED,
67 pending: 0,
68 bits: 0,
69 }
70 }
71}
72
73impl FastHasher {
74 #[inline]
75 fn fold(state: u64, value: u64) -> u64 {
76 let (lo, hi) = (state ^ value).carrying_mul(MUL, 0);
77 lo ^ hi
78 }
79
80 #[inline(always)]
81 fn write_bits(&mut self, value: u64, bits: u32) {
82 if self.bits + bits > 64 {
83 self.state = Self::fold(self.state, self.pending);
84 self.pending = 0;
85 self.bits = 0;
86 }
87
88 self.pending = (self.pending << bits) | value;
89 self.bits += bits;
90 }
91}
92
93impl Hasher for FastHasher {
94 #[inline(always)]
95 fn finish(&self) -> u64 {
96 if self.bits > 0 {
97 Self::fold(self.state, self.pending ^ (u64::from(self.bits & 63) << 56))
99 } else {
100 self.state
101 }
102 }
103
104 #[inline]
105 fn write(&mut self, mut bytes: &[u8]) {
106 while let Some((chunk, rest)) = bytes.split_first_chunk() {
107 self.write_u64(u64::from_ne_bytes(*chunk));
108 bytes = rest;
109 }
110
111 if let Some((chunk, rest)) = bytes.split_first_chunk() {
112 self.write_u32(u32::from_ne_bytes(*chunk));
113 bytes = rest;
114 }
115
116 if let Some((chunk, rest)) = bytes.split_first_chunk() {
117 self.write_u16(u16::from_ne_bytes(*chunk));
118 bytes = rest;
119 }
120
121 if let Some(&byte) = bytes.first() {
122 self.write_u8(byte);
123 }
124 }
125
126 #[inline(always)]
127 fn write_u8(&mut self, n: u8) {
128 self.write_bits(u64::from(n), 8);
129 }
130
131 #[inline(always)]
132 fn write_u16(&mut self, n: u16) {
133 self.write_bits(u64::from(n), 16);
134 }
135
136 #[inline(always)]
137 fn write_u32(&mut self, n: u32) {
138 self.write_bits(u64::from(n), 32);
139 }
140
141 #[inline(always)]
142 fn write_u64(&mut self, n: u64) {
143 self.state = Self::fold(self.state, n);
144 }
145
146 #[inline(always)]
147 #[expect(clippy::cast_possible_truncation)]
148 fn write_u128(&mut self, n: u128) {
149 self.write_u64(n as u64);
150 self.write_u64((n >> 64) as u64);
151 }
152
153 #[inline(always)]
154 #[cfg(target_pointer_width = "16")]
155 #[expect(clippy::cast_possible_truncation)]
156 fn write_usize(&mut self, n: usize) {
157 self.write_u16(n as u16);
158 }
159
160 #[inline(always)]
161 #[cfg(target_pointer_width = "32")]
162 #[expect(clippy::cast_possible_truncation)]
163 fn write_usize(&mut self, n: usize) {
164 self.write_u32(n as u32);
165 }
166
167 #[inline(always)]
168 #[cfg(target_pointer_width = "64")]
169 fn write_usize(&mut self, n: usize) {
170 self.write_u64(n as u64);
171 }
172}
173
174pub type BuildFastHasher = BuildHasherDefault<FastHasher>;
176#[expect(clippy::disallowed_types)]
178pub type FastMap<K, V> = HashMap<K, V, BuildFastHasher>;
179#[expect(clippy::disallowed_types)]
181pub type FastSet<T> = HashSet<T, BuildFastHasher>;
182
183pub trait FastCollectionBuilder {
185 fn new() -> Self;
187
188 fn with_capacity(capacity: usize) -> Self;
190}
191
192#[expect(clippy::implicit_hasher)]
193impl<K, V> FastCollectionBuilder for FastMap<K, V> {
194 #[inline]
195 fn new() -> Self {
196 Self::with_hasher(BuildFastHasher::default())
197 }
198
199 #[inline]
200 fn with_capacity(capacity: usize) -> Self {
201 Self::with_capacity_and_hasher(capacity, BuildFastHasher::default())
202 }
203}
204
205#[expect(clippy::implicit_hasher)]
206impl<T> FastCollectionBuilder for FastSet<T> {
207 #[inline]
208 fn new() -> Self {
209 Self::with_hasher(BuildFastHasher::default())
210 }
211
212 #[inline]
213 fn with_capacity(capacity: usize) -> Self {
214 Self::with_capacity_and_hasher(capacity, BuildFastHasher::default())
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 fn hash_of(f: impl FnOnce(&mut FastHasher)) -> u64 {
223 let mut hasher = FastHasher::default();
224 f(&mut hasher);
225 hasher.finish()
226 }
227
228 #[test]
229 fn packs_small_writes() {
230 let a = hash_of(|h| {
231 h.write_u16(0x1234);
232 h.write_u16(0x5678);
233 h.write_u32(0x9abc_def0);
234 });
235 let b = hash_of(|h| h.write_u64(0x1234_5678_9abc_def0));
236 assert_eq!(a, b);
237 }
238
239 #[test]
240 fn packs_partial_writes_with_length() {
241 let a = hash_of(|h| {
242 h.write_u16(0x1234);
243 h.write_u16(0x5678);
244 h.write_u8(0x9a);
245 });
246 let b = hash_of(|h| h.write_u64(0x2800_0012_3456_789a));
247 assert_eq!(a, b);
248 }
249
250 #[test]
251 fn field_order_matters() {
252 let a = hash_of(|h| {
253 h.write_u32(1);
254 h.write_u32(2);
255 });
256 let packed_a = hash_of(|h| h.write_u64(0x0000_0001_0000_0002));
257 assert_eq!(a, packed_a);
258
259 let b = hash_of(|h| {
260 h.write_u32(2);
261 h.write_u32(1);
262 });
263 let packed_b = hash_of(|h| h.write_u64(0x0000_0002_0000_0001));
264 assert_eq!(b, packed_b);
265
266 assert_ne!(a, b);
267 }
268
269 #[test]
270 fn packs_full_word_then_tags_tail() {
271 let a = hash_of(|h| {
272 h.write_u32(1);
273 h.write_u32(2);
274 h.write_u32(3);
275 });
276 let b = hash_of(|h| {
277 h.write_u64(0x0000_0001_0000_0002);
278 h.write_u64(0x2000_0000_0000_0003);
279 });
280 assert_eq!(a, b);
281 }
282
283 #[test]
284 fn u64_write_order_matters() {
285 let a = hash_of(|h| {
286 h.write_u64(1);
287 h.write_u64(2);
288 h.write_u64(3);
289 });
290 let b = hash_of(|h| {
291 h.write_u64(3);
292 h.write_u64(2);
293 h.write_u64(1);
294 });
295 assert_ne!(a, b);
296 }
297
298 #[test]
299 fn zero_bytes_are_significant() {
300 let inputs: &[&[u8]] = &[
301 b"a",
302 b"ab",
303 b"ab\0",
304 b"a\0\0\0",
305 b"\0a",
306 b"\0a\0",
307 b"\0\0\0a",
308 b"\0\0\0\0\0\0\0\0a",
309 b"a\0\0\0\0\0\0\0\0",
310 b"\0",
311 b"\0\0",
312 b"\0\0\0",
313 b"\0\0\0\0",
314 b"\0\0\0\0\0\0\0\0",
315 b"\0\0\0\0\0\0\0\0\0",
316 b"\0\0\0\0\0\0\0\0\0\0",
317 ];
318 for (i, a) in inputs.iter().enumerate() {
319 for b in &inputs[i + 1..] {
320 assert_ne!(
321 hash_of(|h| h.write(a)),
322 hash_of(|h| h.write(b)),
323 "{a:?} and {b:?} hashed the same",
324 );
325 }
326 }
327 }
328}