Skip to main content

hashconsing/
hash_coll.rs

1#![allow(clippy::needless_doctest_main)]
2//! Efficient hash collections for hashconsed data.
3//!
4//! This module provide hash set and hash map types with simple hash functions for hashconsed types.
5//! The hash of an hashconsed value is its unique identifier, multiplied by a large prime. This is
6//! obviously extremely dangerous from a security point of view: these collections should **never**
7//! be used for cryptographic purposes.
8//!
9//! Note that you can also use `BTreeMap` and `BTreeSet` on hashconsed types since they are totally
10//! ordered.
11//!
12//! # Hashers
13//!
14//! This crate provides various hashers. The main discussion about this crate's hashers is in [PR 8
15//! on github].
16//!
17//! [PR 8 on github]: https://github.com/AdrienChampion/hashconsing/pull/8 (repo's PR 8 on github)
18//!
19//! This module exposes hash-collections `HConSet` and `HConMap`, both parameterized by a hash
20//! implementation. This module provides the following hashers, ordered from least efficient to most
21//! efficient:
22//!
23//! - [`id_hash`](./hashers/id_hash): deterministic, inefficient, mostly here for legacy reasons;
24//! - [`sip_hash`](./hashers/sip_hash): Rust's standard hasher, non-deterministic;
25//! - [`p_hash`](./hashers/p_hash): deterministic and efficient, replaces the obsolete `id_hash`;
26//! - [`a_hash`](./hashers/a_hash): non-deterministic and very efficient hasher, requires the
27//!   activation of feature `with_ahash`.
28//!
29//! The default hasher for `HConSet` and `HConMap` is `p_hash`.
30//!
31//! This module exposes a dedicated sub-module for each of these hashers that re-exports `HConSet`
32//! and `HConMap` while forcing the appropriate hasher.
33//!
34//!
35//! # Usage
36//!
37//! > TL;DR You need to specify the hashconsed type when creating one of the collections in this
38//! > module.
39//!
40//! There is a bit of internal gymnastic so that the type signatures of these collections are
41//! natural. If `Term` is the hashconsed version of `RTerm`, then you want the type of the sets to
42//! be the natural one, *e.g.* `HConSet<Term>`.
43//!
44//! However, since `Term` is really an alias for `HConsed<RTerm>`, then if we wanted to declare
45//! `HConSet` as an alias for `HashSet` we would get `type HConSet<Inner> = HashSet<HConsed<Inner>>`
46//! (omitting the custom hasher). That is, our sets would have type `HConSet<RTerm>`, which is not
47//! very pretty. We could just define an alias though: `type TermSet = HConSet<RTerm>`, but it turns
48//! out it's better to wrap the actual set in a `struct` anyway. Mostly to be able to define `new`
49//! and `with_capacity` without relying on a trait (users would need to import) to do that.
50//!
51//! So actually `HConsed` types automatically implement the internal `trait HashConsed { type Inner;
52//! }`. The sole purpose of this trait (currently) is to pass the inner type implicitly thanks to a
53//! `T: HashConsed` bound. Rust's type inference does not seem to really like this, and struggles a
54//! bit to infer the types at play. In practice, it means that you need to specify the type of the
55//! hashconsed elements in your set/map.
56//!
57//! ```
58//! use hashconsing::{*, hash_coll::default::*};
59//!
60//! #[derive(Hash, Clone, PartialEq, Eq)]
61//! enum ActualTerm {
62//!   Var(usize),
63//!   Lam(Term),
64//!   App(Term, Term)
65//! }
66//! type Term = HConsed<ActualTerm>;
67//!
68//! let mut consign = HConsign::empty();
69//! assert_eq!(consign.len(), 0);
70//!
71//! let mut map: HConMap<Term,_> = HConMap::with_capacity(100);
72//! let mut set: HConSet<Term> = HConSet::with_capacity(100);
73//!
74//! let (v1, v1_name) = (
75//!   consign.mk( ActualTerm::Var(0) ), "v1"
76//! );
77//! assert_eq!(consign.len(), 1);
78//! let prev = map.insert(v1.clone(), v1_name);
79//! assert_eq!( prev, None );
80//! let is_new = set.insert(v1.clone());
81//! assert!( is_new );
82//! ```
83//!
84//! The problem completely goes away if you redefine your set/map type, and is the recommended way
85//! of using these collections.
86//!
87//! ```
88//! use hashconsing::{*, hash_coll::*};
89//!
90//! #[derive(Hash, Clone, PartialEq, Eq)]
91//! enum ActualTerm {
92//!   Var(usize),
93//!   Lam(Term),
94//!   App(Term, Term)
95//! }
96//! type Term = HConsed<ActualTerm>;
97//! type TermMap<T> = HConMap<Term, T>;
98//! type TermSet = HConSet<Term>;
99//!
100//! let mut consign = HConsign::empty();
101//! assert_eq!(consign.len(), 0);
102//!
103//! let mut map = TermMap::with_capacity(100);
104//! let mut set = TermSet::with_capacity(100);
105//!
106//! let (v1, v1_name) = (
107//!   consign.mk( ActualTerm::Var(0) ), "v1"
108//! );
109//! assert_eq!(consign.len(), 1);
110//! let prev = map.insert(v1.clone(), v1_name);
111//! assert_eq!( prev, None );
112//! let is_new = set.insert(v1.clone());
113//! assert!( is_new );
114//! ```
115//!
116//! One can modify the hash use by term maps and sets, as well as the term map underlying a
117//! consignment.
118//!
119//! ```
120//! use hashconsing::{*, hash_coll::*};
121//! use std::collections::hash_map::RandomState;
122//!
123//! #[derive(Hash,PartialEq,Eq,Clone)]
124//! struct ActualSum(Vec<Sum>);
125//! type Sum = HConsed<ActualSum>;
126//!
127//! consign! {
128//!     /// Factory for terms. Uses the standard library's "SipHash".
129//!     let factory = consign(37, RandomState::new()) for ActualSum;
130//! }
131//!
132//! fn main() {
133//!     // Map from terms. Uses the standard library's "SipHash".
134//!     let map = HConMap::<Sum, usize, RandomState>::with_hasher(RandomState::new());
135//! }
136//! ```
137use std::{
138    collections::{HashMap, HashSet},
139    hash::{BuildHasher, Hash, Hasher},
140    ops::{Deref, DerefMut},
141};
142
143pub mod hashers;
144
145/// Hash sets (maps) for (from) hconsed elements using [`ahash`].
146///
147/// > **NB:** this module is empty if the `"with_ahash"` feature is not active.
148///
149/// [`ahash`]: https://crates.io/crates/ahash (ahash on crates.io)
150pub mod a_hash {
151    /// Set of hashconsed elements.
152    #[cfg(feature = "with_ahash")]
153    pub type HConSet<T> = super::HConSet<T, super::hashers::a_hash::Builder>;
154    /// Map from hashconsed elements.
155    #[cfg(feature = "with_ahash")]
156    pub type HConMap<K, V> = super::HConMap<K, V, super::hashers::a_hash::Builder>;
157}
158/// Hash sets (maps) for (from) hconsed elements using Rust's `std` hasher.
159pub mod sip_hash {
160    /// Set of hashconsed elements.
161    pub type HConSet<T> = super::HConSet<T, super::hashers::sip_hash::Builder>;
162    /// Map from hashconsed elements.
163    pub type HConMap<K, V> = super::HConMap<K, V, super::hashers::sip_hash::Builder>;
164}
165/// Hash sets (maps) for (from) hconsed elements using [`p-hash`].
166///
167/// [`p-hash`]: https://github.com/AdrienChampion/hashconsing/pull/8 (p-hash in PR 8 on github)
168pub mod p_hash {
169    /// Set of hashconsed elements.
170    pub type HConSet<T> = super::HConSet<T, super::hashers::p_hash::Builder>;
171    /// Map from hashconsed elements.
172    pub type HConMap<K, V> = super::HConMap<K, V, super::hashers::p_hash::Builder>;
173}
174/// Hash sets (maps) for (from) hconsed elements using [`id-hash`].
175///
176/// [`id-hash`]: https://github.com/AdrienChampion/hashconsing/pull/8 (p-hash in PR 8 on github)
177pub mod id_hash {
178    /// Set of hashconsed elements.
179    pub type HConSet<T> = super::HConSet<T, super::hashers::id_hash::Builder>;
180    /// Map from hashconsed elements.
181    pub type HConMap<K, V> = super::HConMap<K, V, super::hashers::id_hash::Builder>;
182}
183/// Default hash sets (maps) for (from) hconsed elements.
184///
185/// Just a re-export of [`p_hash`](../p_hash/).
186pub mod default {
187    pub use super::p_hash::*;
188}
189
190use crate::{HConsed, HashConsed};
191
192/// A hash set of hash-consed things.
193///
194/// Its second argument is a [`BuildHasher`] which should be used to construct the hash function
195/// for this set. The default is an in-crate hash function which just multiplies the identifier of
196/// the hash-consed type by a large prime.
197#[derive(Clone, Debug)]
198pub struct HConSet<T, S = self::hashers::p_hash::Builder>
199where
200    T: HashConsed,
201    T::Inner: Eq + Hash,
202{
203    set: HashSet<HConsed<T::Inner>, S>,
204}
205
206impl<T, S> PartialEq for HConSet<T, S>
207where
208    T: HashConsed,
209    T::Inner: Eq + Hash,
210    S: BuildHasher,
211{
212    fn eq(&self, other: &Self) -> bool {
213        self.len() == other.len() && self.iter().zip(other.iter()).all(|(e_1, e_2)| e_1 == e_2)
214    }
215}
216
217impl<T, S> Eq for HConSet<T, S>
218where
219    T: HashConsed,
220    T::Inner: Eq + Hash,
221    S: BuildHasher,
222{
223}
224
225impl<T, S> Hash for HConSet<T, S>
226where
227    T: HashConsed,
228    T::Inner: Eq + Hash,
229    S: BuildHasher,
230{
231    fn hash<H>(&self, h: &mut H)
232    where
233        H: Hasher,
234    {
235        for elem in self {
236            elem.hash(h)
237        }
238    }
239}
240
241impl<T, S> Default for HConSet<T, S>
242where
243    T: HashConsed,
244    T::Inner: Eq + Hash,
245    S: Default,
246{
247    fn default() -> Self {
248        HConSet {
249            set: HashSet::with_hasher(S::default()),
250        }
251    }
252}
253
254macro_rules! hashplement {
255    {$(
256        $(#[$meta:meta])*
257        $hcoll_name:ident <
258            $key_t_param:ident
259            $(, $val_t_param:ident)?
260        > for $hasher:ty ;
261    )*} => {$(
262        $(#[$meta])*
263        impl<$key_t_param $(, $val_t_param)?> $hcoll_name<
264            $key_t_param $(, $val_t_param)?, $hasher
265        >
266        where
267            $key_t_param: HashConsed,
268            $key_t_param::Inner: Eq + Hash,
269        {
270            /// Empty constructor.
271            #[inline]
272            pub fn new() -> Self {
273                $hcoll_name::with_hasher(<$hasher>::new())
274            }
275            /// Empty constructor with capacity.
276            pub fn with_capacity(capa: usize) -> Self {
277                $hcoll_name::with_capacity_and_hasher(capa, <$hasher>::new())
278            }
279        }
280    )*};
281}
282
283hashplement! {
284    #[cfg(feature = "with_ahash")]
285    HConSet<T> for self::hashers::a_hash::Builder;
286    HConSet<T> for self::hashers::sip_hash::Builder;
287    HConSet<T> for self::hashers::p_hash::Builder;
288    HConSet<T> for self::hashers::id_hash::Builder;
289}
290
291impl<T, S> HConSet<T, S>
292where
293    T: HashConsed,
294    T::Inner: Eq + Hash,
295    S: BuildHasher,
296{
297    /// An empty set of hashconsed things, using a custom hash.
298    ///
299    /// See [`BuildHasher`] for the trait that `build_hasher` must implement, or
300    /// [`HConMap::with_hasher`] for an example.
301    #[inline]
302    pub fn with_hasher(build_hasher: S) -> Self {
303        HConSet {
304            set: HashSet::with_hasher(build_hasher),
305        }
306    }
307    /// An empty set of hashconsed things with a capacity and a custom hash.
308    ///
309    /// See [`BuildHasher`] for the trait that `build_hasher` must implement.
310    #[inline]
311    pub fn with_capacity_and_hasher(capa: usize, build_hasher: S) -> Self {
312        HConSet {
313            set: HashSet::with_capacity_and_hasher(capa, build_hasher),
314        }
315    }
316}
317
318impl<T, S> HConSet<T, S>
319where
320    T: HashConsed,
321    T::Inner: Eq + Hash,
322{
323    /// An iterator visiting all elements.
324    #[inline]
325    pub fn iter(&self) -> ::std::collections::hash_set::Iter<'_, HConsed<T::Inner>> {
326        self.set.iter()
327    }
328}
329
330impl<'a, T, S> IntoIterator for &'a HConSet<T, S>
331where
332    T: HashConsed,
333    T::Inner: Hash + Eq,
334{
335    type Item = &'a HConsed<T::Inner>;
336    type IntoIter = ::std::collections::hash_set::Iter<'a, HConsed<T::Inner>>;
337    fn into_iter(self) -> Self::IntoIter {
338        self.set.iter()
339    }
340}
341impl<T, S> IntoIterator for HConSet<T, S>
342where
343    T: HashConsed,
344    T::Inner: Hash + Eq,
345{
346    type Item = HConsed<T::Inner>;
347    type IntoIter = ::std::collections::hash_set::IntoIter<HConsed<T::Inner>>;
348    fn into_iter(self) -> Self::IntoIter {
349        self.set.into_iter()
350    }
351}
352impl<T, S> ::std::iter::FromIterator<HConsed<T::Inner>> for HConSet<T, S>
353where
354    T: HashConsed,
355    T::Inner: Hash + Eq,
356    S: BuildHasher + Default,
357{
358    fn from_iter<I: IntoIterator<Item = HConsed<T::Inner>>>(iter: I) -> Self {
359        HConSet {
360            set: HashSet::from_iter(iter),
361        }
362    }
363}
364impl<T, S> Deref for HConSet<T, S>
365where
366    T: HashConsed,
367    T::Inner: Hash + Eq,
368{
369    type Target = HashSet<HConsed<T::Inner>, S>;
370    fn deref(&self) -> &Self::Target {
371        &self.set
372    }
373}
374impl<T, S> DerefMut for HConSet<T, S>
375where
376    T: HashConsed,
377    T::Inner: Hash + Eq,
378{
379    fn deref_mut(&mut self) -> &mut Self::Target {
380        &mut self.set
381    }
382}
383
384impl<T, Src, S> From<Src> for HConSet<HConsed<T>, S>
385where
386    T: Hash + Eq,
387    Src: Iterator<Item = HConsed<T>>,
388    S: Default + BuildHasher,
389{
390    fn from(src: Src) -> Self {
391        let mut set = HConSet::default();
392        for elem in src {
393            set.insert(elem);
394        }
395        set
396    }
397}
398
399/// A hash map of hash-consed things.
400///
401/// Its second argument is a [`BuildHasher`] which should be used to construct the hash function
402/// for this set. The default is an in-crate hash function which just multiplies the identifier of
403/// the hash-consed type by a large prime.
404#[derive(Clone, Debug)]
405pub struct HConMap<T, V, S = self::hashers::p_hash::Builder>
406where
407    T: HashConsed,
408    T::Inner: Hash + Eq,
409{
410    map: HashMap<HConsed<T::Inner>, V, S>,
411}
412
413impl<T, V, S> PartialEq for HConMap<T, V, S>
414where
415    T: HashConsed,
416    T::Inner: Eq + Hash,
417    V: Eq,
418    S: BuildHasher,
419{
420    fn eq(&self, other: &Self) -> bool {
421        self.len() == other.len()
422            && self
423                .iter()
424                .zip(other.iter())
425                .all(|((k_1, v_1), (k_2, v_2))| k_1 == k_2 && v_1 == v_2)
426    }
427}
428
429impl<T, V, S> Eq for HConMap<T, V, S>
430where
431    T: HashConsed,
432    T::Inner: Eq + Hash,
433    V: Eq,
434    S: BuildHasher,
435{
436}
437
438impl<T, V, S> Hash for HConMap<T, V, S>
439where
440    T: HashConsed,
441    T::Inner: Eq + Hash,
442    V: Hash,
443    S: BuildHasher,
444{
445    fn hash<H>(&self, h: &mut H)
446    where
447        H: Hasher,
448    {
449        for (key, value) in self {
450            key.hash(h);
451            value.hash(h)
452        }
453    }
454}
455
456impl<T, V, S> Default for HConMap<T, V, S>
457where
458    T: HashConsed,
459    T::Inner: Eq + Hash,
460    S: Default,
461{
462    fn default() -> Self {
463        HConMap {
464            map: HashMap::default(),
465        }
466    }
467}
468
469hashplement! {
470    #[cfg(feature = "with_ahash")]
471    HConMap<K, V> for self::hashers::a_hash::Builder;
472    HConMap<K, V> for self::hashers::sip_hash::Builder;
473    HConMap<K, V> for self::hashers::p_hash::Builder;
474    HConMap<K, V> for self::hashers::id_hash::Builder;
475}
476
477impl<T, V, S> HConMap<T, V, S>
478where
479    T: HashConsed,
480    T::Inner: Eq + Hash,
481    S: BuildHasher,
482{
483    /// An empty map of hashconsed things, using a custom hash.
484    ///
485    /// See [`BuildHasher`] for the trait that `build_hasher` must implement.
486    ///
487    /// ## Example
488    ///
489    /// ```
490    /// use std::collections::hash_map::RandomState;
491    /// use hashconsing::{HConsed, hash_coll::HConMap};
492    ///
493    /// #[derive(Hash,PartialEq,Eq)]
494    /// struct ActualSum(Vec<Sum>);
495    /// type Sum = HConsed<ActualSum>;
496    ///
497    /// // Build map with standard library's hash builder (SipHash)
498    /// let map = HConMap::<Sum, usize, _>::with_hasher(RandomState::new());
499    /// ```
500    #[inline]
501    pub fn with_hasher(build_hasher: S) -> Self {
502        HConMap {
503            map: HashMap::with_hasher(build_hasher),
504        }
505    }
506    /// An empty map of hashconsed things with a capacity and a custom hash.
507    ///
508    /// See [`BuildHasher`] for the trait that `build_hasher` must implement.
509    #[inline]
510    pub fn with_capacity_and_hasher(capa: usize, build_hasher: S) -> Self {
511        HConMap {
512            map: HashMap::with_capacity_and_hasher(capa, build_hasher),
513        }
514    }
515}
516
517impl<T: HashConsed, V, S> HConMap<T, V, S>
518where
519    T::Inner: Hash + Eq,
520{
521    /// An iterator visiting all elements.
522    #[inline]
523    pub fn iter(&self) -> ::std::collections::hash_map::Iter<'_, HConsed<T::Inner>, V> {
524        self.map.iter()
525    }
526    /// An iterator visiting all elements.
527    #[inline]
528    pub fn iter_mut(&mut self) -> ::std::collections::hash_map::IterMut<'_, HConsed<T::Inner>, V> {
529        self.map.iter_mut()
530    }
531}
532
533impl<'a, T, V, S> IntoIterator for &'a HConMap<T, V, S>
534where
535    T: HashConsed,
536    T::Inner: Hash + Eq,
537{
538    type Item = (&'a HConsed<T::Inner>, &'a V);
539    type IntoIter = ::std::collections::hash_map::Iter<'a, HConsed<T::Inner>, V>;
540    fn into_iter(self) -> Self::IntoIter {
541        self.map.iter()
542    }
543}
544impl<'a, T, V, S> IntoIterator for &'a mut HConMap<T, V, S>
545where
546    T: HashConsed,
547    T::Inner: Hash + Eq,
548{
549    type Item = (&'a HConsed<T::Inner>, &'a mut V);
550    type IntoIter = ::std::collections::hash_map::IterMut<'a, HConsed<T::Inner>, V>;
551    fn into_iter(self) -> Self::IntoIter {
552        self.map.iter_mut()
553    }
554}
555impl<T, V, S> IntoIterator for HConMap<T, V, S>
556where
557    T: HashConsed,
558    T::Inner: Hash + Eq,
559{
560    type Item = (HConsed<T::Inner>, V);
561    type IntoIter = ::std::collections::hash_map::IntoIter<HConsed<T::Inner>, V>;
562    fn into_iter(self) -> Self::IntoIter {
563        self.map.into_iter()
564    }
565}
566impl<T, V, S> ::std::iter::FromIterator<(HConsed<T::Inner>, V)> for HConMap<T, V, S>
567where
568    T: HashConsed,
569    T::Inner: Hash + Eq,
570    S: Default + BuildHasher,
571{
572    fn from_iter<I: IntoIterator<Item = (HConsed<T::Inner>, V)>>(iter: I) -> Self {
573        HConMap {
574            map: HashMap::from_iter(iter),
575        }
576    }
577}
578impl<T: HashConsed, V, S> Deref for HConMap<T, V, S>
579where
580    T::Inner: Hash + Eq,
581{
582    type Target = HashMap<HConsed<T::Inner>, V, S>;
583    fn deref(&self) -> &Self::Target {
584        &self.map
585    }
586}
587impl<T: HashConsed, V, S> DerefMut for HConMap<T, V, S>
588where
589    T::Inner: Hash + Eq,
590{
591    fn deref_mut(&mut self) -> &mut Self::Target {
592        &mut self.map
593    }
594}
595
596impl<T, V, Src, S> From<Src> for HConMap<HConsed<T>, V, S>
597where
598    T: Hash + Eq,
599    Src: Iterator<Item = (HConsed<T>, V)>,
600    S: BuildHasher + Default,
601{
602    fn from(src: Src) -> Self {
603        let mut set = HConMap::default();
604        for (elem, value) in src {
605            set.insert(elem, value);
606        }
607        set
608    }
609}
610
611/// Simple hash for `usize`s and `u64`s---simply multiplies the number by a fixed 64-bit
612/// prime. The former is used for wrapped indices, the latter for hashconsed things.
613///
614/// This is kind of unsafe, in a way. The hasher will cause logic errors if
615/// asked to hash anything else than what it was supposed to hash.
616///
617/// In `debug`, this is actually checked each time something is hashed. This
618/// check is of course deactivated in `release`.
619mod hash {
620    use std::hash::{BuildHasher, Hasher};
621
622    /// Empty struct used to build `HashU64`.
623    #[derive(Clone, Default)]
624    pub struct BuildHashU64 {}
625    impl BuildHashU64 {
626        #[allow(dead_code)]
627        pub fn new() -> Self {
628            Self {}
629        }
630    }
631    impl BuildHasher for BuildHashU64 {
632        type Hasher = HashU64;
633        fn build_hasher(&self) -> HashU64 {
634            HashU64 { buf: [0; 8] }
635        }
636    }
637
638    /// Simple hasher for `usize`. Multiplies the number by: `0xDA5DF7A7BD02F2C7u64`, a 64-bit
639    /// prime. **This hasher is only for hashing `usize`s**.
640    pub struct HashU64 {
641        buf: [u8; 8],
642    }
643    impl HashU64 {
644        /// Checks that a slice of bytes has the length of a `usize`. Only active
645        /// in debug.
646        #[cfg(debug_assertions)]
647        #[inline(always)]
648        fn test_bytes(bytes: &[u8]) {
649            if bytes.len() != 8 {
650                panic!(
651                    "[illegal] `HashU64::hash` \
652                     called with non-`u64` argument ({} bytes, expected {})",
653                    bytes.len(),
654                    8
655                )
656            }
657        }
658        /// Checks that a slice of bytes has the length of a `usize`. Only active
659        /// in debug.
660        #[cfg(not(debug_assertions))]
661        #[inline(always)]
662        fn test_bytes(_: &[u8]) {}
663    }
664    impl Hasher for HashU64 {
665        fn finish(&self) -> u64 {
666            let block: u64 = u64::from_ne_bytes(self.buf);
667            // Multiply by random 64-bit prime to distribute
668            block.wrapping_mul(0xDA5DF7A7BD02F2C7u64)
669        }
670        fn write(&mut self, bytes: &[u8]) {
671            Self::test_bytes(bytes);
672            self.buf[..8].clone_from_slice(&bytes[..8])
673        }
674    }
675}