Skip to content

Commit 001a04f

Browse files
committed
Add Prehashed type
1 parent eadbcf2 commit 001a04f

File tree

3 files changed

+128
-1
lines changed

3 files changed

+128
-1
lines changed

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
mod cache;
44
mod constraint;
55
mod input;
6+
mod prehashed;
67
mod track;
78

9+
pub use crate::prehashed::Prehashed;
810
pub use crate::track::{Track, Tracked};
911
pub use comemo_macros::{memoize, track};
1012

src/prehashed.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use std::any::Any;
2+
use std::cmp::{Ord, PartialOrd};
3+
use std::fmt::{self, Debug, Formatter};
4+
use std::hash::{Hash, Hasher};
5+
use std::ops::Deref;
6+
7+
use siphasher::sip128::{Hasher128, SipHasher};
8+
9+
/// A wrapper type with precomputed hash.
10+
///
11+
/// This is useful if you want to pass large values of `T` to memoized
12+
/// functions. Especially recursive structures like trees benefit from
13+
/// intermediate prehashed nodes.
14+
///
15+
/// # `Hash` and `Eq`
16+
/// When implementing both `Hash` and `Eq`, the following property [must
17+
/// hold][property]:
18+
/// ```
19+
/// a == b -> hash(a) == hash(b)
20+
/// ```
21+
/// The inverse implication does not follow from this immediately. However,
22+
/// _comemo_ uses high-quality 128 bit hashes in all places. This reduces the
23+
/// risk of a hash collision to an absolute minimum. Therefore, this trait
24+
/// additionally provides `PartialEq` and `Eq` implementations that compare by
25+
/// hash instead of by value.
26+
///
27+
/// # Notice
28+
/// Note that for a value `v` of type `T`, `hash(v)` is not necessarily equal to
29+
/// `hash(Prehashed::new(v))`. Writing the precomputed hash into a hasher's
30+
/// state produces different output than writing the value's parts directly.
31+
/// However, that seldomly matters as you are typically either dealing with
32+
/// values of type `T` or with values of type `Prehashed<T>`, not a mix of both.
33+
///
34+
/// [property]: https://doc.rust-lang.org/std/hash/trait.Hash.html
35+
#[derive(Copy, Clone)]
36+
pub struct Prehashed<T: ?Sized> {
37+
/// The precomputed hash.
38+
hash: u128,
39+
/// The wrapped item.
40+
item: T,
41+
}
42+
43+
impl<T: Hash + 'static> Prehashed<T> {
44+
/// Compute an item's hash and wrap it.
45+
#[inline]
46+
pub fn new(item: T) -> Self {
47+
Self {
48+
hash: {
49+
// Also hash the TypeId because the type might be converted
50+
// through an unsized coercion.
51+
let mut state = SipHasher::new();
52+
item.type_id().hash(&mut state);
53+
item.hash(&mut state);
54+
state.finish128().as_u128()
55+
},
56+
item,
57+
}
58+
}
59+
60+
/// Return the wrapped value.
61+
#[inline]
62+
pub fn into_inner(self) -> T {
63+
self.item
64+
}
65+
}
66+
67+
impl<T: ?Sized> Deref for Prehashed<T> {
68+
type Target = T;
69+
70+
#[inline]
71+
fn deref(&self) -> &Self::Target {
72+
&self.item
73+
}
74+
}
75+
76+
impl<T: Hash + 'static> From<T> for Prehashed<T> {
77+
#[inline]
78+
fn from(value: T) -> Self {
79+
Self::new(value)
80+
}
81+
}
82+
83+
impl<T: ?Sized> Hash for Prehashed<T> {
84+
#[inline]
85+
fn hash<H: Hasher>(&self, state: &mut H) {
86+
state.write_u128(self.hash);
87+
}
88+
}
89+
90+
impl<T: Debug + ?Sized> Debug for Prehashed<T> {
91+
#[inline]
92+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
93+
self.item.fmt(f)
94+
}
95+
}
96+
97+
impl<T: Default + Hash + 'static> Default for Prehashed<T> {
98+
#[inline]
99+
fn default() -> Self {
100+
Self::new(T::default())
101+
}
102+
}
103+
104+
impl<T: Eq + ?Sized> Eq for Prehashed<T> {}
105+
106+
impl<T: PartialEq + ?Sized> PartialEq for Prehashed<T> {
107+
#[inline]
108+
fn eq(&self, other: &Self) -> bool {
109+
self.hash == other.hash
110+
}
111+
}
112+
113+
impl<T: Ord + ?Sized> Ord for Prehashed<T> {
114+
#[inline]
115+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
116+
self.item.cmp(&other.item)
117+
}
118+
}
119+
120+
impl<T: PartialOrd + ?Sized> PartialOrd for Prehashed<T> {
121+
#[inline]
122+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
123+
self.item.partial_cmp(&other.item)
124+
}
125+
}

tests/kinds.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use comemo::Track;
33
#[test]
44
fn test_kinds() {
55
let mut tester = Tester { data: "Hi".to_string() };
6-
let tracky = tester.track();
76

7+
let tracky = tester.track();
88
unconditional(tracky); // [Miss] Never called.
99
unconditional(tracky); // [Hit] Nothing changed.
1010
conditional(tracky, "World"); // [Miss] The cache is empty.

0 commit comments

Comments
 (0)