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}