Skip to main content

ixa/
hashing.rs

1//! This module provides a deterministic hasher and `HashMap` and `HashSet` variants that use
2//! it. The hashing data structures in the standard library are not deterministic:
3//!
4//! > By default, HashMap uses a hashing algorithm selected to provide
5//! > resistance against HashDoS attacks. The algorithm is randomly seeded, and a
6//! > reasonable best-effort is made to generate this seed from a high quality,
7//! > secure source of randomness provided by the host without blocking the program.
8//!
9//! The standard library `HashMap` has a `new` method, but `HashMap<K, V, S>` does not have a `new`
10//! method by default. Use `HashMap::default()` instead to create a new hashmap with the default
11//! hasher. If you really need to keep the API the same across implementations, we provide the
12//! `HashMapExt` trait extension. Similarly, for `HashSet` and `HashSetExt`.The traits need only be
13//! in scope.
14//!
15
16use std::hash::{BuildHasherDefault, Hash, Hasher};
17
18pub use indexmap::set::Iter as IndexSetIter;
19use indexmap::IndexSet as RawIndexSet;
20pub use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet, FxHasher};
21use xxhash_rust::xxh3::Xxh3Default;
22
23type FxBuildHasher = BuildHasherDefault<FxHasher>;
24
25pub type IndexSet<T> = RawIndexSet<T, FxBuildHasher>;
26
27pub type HashValueType = u128;
28
29pub(crate) type DeterministicHasher = Xxh3Default;
30
31/// A `rkyv` writer that streams serialized bytes directly into a `Hasher`.
32pub struct HasherWriter<'a, H> {
33    hasher: &'a mut H,
34    pos: usize,
35}
36
37impl<'a, H> HasherWriter<'a, H> {
38    #[must_use]
39    pub fn new(hasher: &'a mut H) -> Self {
40        Self { hasher, pos: 0 }
41    }
42}
43
44impl<H: Hasher> rkyv::ser::Positional for HasherWriter<'_, H> {
45    fn pos(&self) -> usize {
46        self.pos
47    }
48}
49
50impl<H: Hasher> rkyv::ser::Writer<rkyv::rancor::Error> for HasherWriter<'_, H> {
51    fn write(&mut self, bytes: &[u8]) -> Result<(), rkyv::rancor::Error> {
52        self.hasher.write(bytes);
53        self.pos += bytes.len();
54        Ok(())
55    }
56}
57
58/// A fixed-size `rkyv` writer used by macro-generated equality implementations.
59#[derive(Debug, Clone, Copy)]
60pub struct EqualityBufferWriter<const N: usize> {
61    buf: [u8; N],
62    pos: usize,
63}
64
65impl<const N: usize> EqualityBufferWriter<N> {
66    #[must_use]
67    pub fn new() -> Self {
68        Self {
69            buf: [0; N],
70            pos: 0,
71        }
72    }
73
74    #[must_use]
75    pub fn as_written(&self) -> &[u8] {
76        &self.buf[..self.pos]
77    }
78}
79
80impl<const N: usize> Default for EqualityBufferWriter<N> {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86impl<const N: usize> rkyv::ser::Positional for EqualityBufferWriter<N> {
87    fn pos(&self) -> usize {
88        self.pos
89    }
90}
91
92impl<const N: usize, E: rkyv::rancor::Source> rkyv::ser::Writer<E> for EqualityBufferWriter<N> {
93    fn write(&mut self, bytes: &[u8]) -> Result<(), E> {
94        let end = self.pos + bytes.len();
95        assert!(
96            end <= N,
97            "serialized form exceeded fixed buffer size: {} > {}",
98            end,
99            N
100        );
101        self.buf[self.pos..end].copy_from_slice(bytes);
102        self.pos = end;
103        Ok(())
104    }
105}
106
107/// Provides API parity with `std::collections::HashMap`.
108pub trait HashMapExt {
109    #[must_use]
110    fn new() -> Self;
111}
112
113impl<K, V> HashMapExt for HashMap<K, V> {
114    fn new() -> Self {
115        HashMap::default()
116    }
117}
118
119// Note that trait aliases are not yet stabilized in rustc.
120// See https://github.com/rust-lang/rust/issues/41517
121/// Provides API parity with `std::collections::HashSet`.
122pub trait HashSetExt {
123    type Item;
124
125    #[must_use]
126    fn new() -> Self;
127
128    /// Equivalent to `self.iter().cloned().collect::<Vec<_>>()`.
129    #[must_use]
130    fn to_owned_vec(&self) -> Vec<Self::Item>;
131}
132
133impl<T: Clone> HashSetExt for HashSet<T> {
134    type Item = T;
135
136    fn new() -> Self {
137        HashSet::default()
138    }
139
140    fn to_owned_vec(&self) -> Vec<Self::Item> {
141        self.iter().cloned().collect()
142    }
143}
144
145impl<T: Clone> HashSetExt for IndexSet<T> {
146    type Item = T;
147
148    fn new() -> Self {
149        IndexSet::default()
150    }
151
152    fn to_owned_vec(&self) -> Vec<Self::Item> {
153        self.iter().cloned().collect()
154    }
155}
156
157/// A convenience method to compute the hash of a `&str`.
158#[must_use]
159pub fn hash_str(data: &str) -> u64 {
160    let mut hasher = rustc_hash::FxHasher::default();
161    hasher.write(data.as_bytes());
162    hasher.finish()
163}
164
165/// This if factored out for cases of implementations using the `Hash` trait to compute a 128-bit
166/// hash. This provides a unified interface to the implementation-specific 128-bit "finish" method.
167#[must_use]
168pub(crate) fn finish_deterministic_hash_128(hasher: DeterministicHasher) -> HashValueType {
169    hasher.digest128()
170}
171
172/// Helper for any `T: Hash` using the crate's deterministic hasher.
173#[must_use]
174pub fn one_shot_128<T: Hash>(value: &T) -> u128 {
175    let mut hasher = DeterministicHasher::default();
176    value.hash(&mut hasher);
177    finish_deterministic_hash_128(hasher)
178}
179
180#[cfg(test)]
181mod tests {
182    use std::hash::Hash;
183
184    use super::*;
185
186    #[test]
187    fn hashes_strings() {
188        let a = one_shot_128(&"hello");
189        let b = one_shot_128(&"hello");
190        let c = one_shot_128(&"world");
191        assert_eq!(a, b);
192        assert_ne!(a, c);
193    }
194
195    #[test]
196    fn hashes_structs() {
197        #[derive(Hash)]
198        struct S {
199            x: u32,
200            y: String,
201        }
202        let h1 = one_shot_128(&S {
203            x: 1,
204            y: "a".into(),
205        });
206        let h2 = one_shot_128(&S {
207            x: 1,
208            y: "a".into(),
209        });
210        assert_eq!(h1, h2);
211    }
212
213    #[test]
214    fn hashing_tuple_matches_hashing_components_in_order() {
215        let tuple = ("John", 25_u32, true);
216
217        let mut hasher = DeterministicHasher::default();
218        tuple.0.hash(&mut hasher);
219        tuple.1.hash(&mut hasher);
220        tuple.2.hash(&mut hasher);
221
222        assert_eq!(finish_deterministic_hash_128(hasher), one_shot_128(&tuple));
223    }
224}