agdb/utilities/
stable_hash.rs1use std::ops::BitXor;
2
3const HASH_CONSTANT: u64 = 0x517cc1b727220a95;
4
5pub trait StableHash {
6 fn stable_hash(&self) -> u64;
7
8 fn add_to_hash(hash: &mut u64, value: u64) {
9 *hash = hash
10 .rotate_left(5)
11 .bitxor(value)
12 .wrapping_mul(HASH_CONSTANT);
13 }
14}
15
16impl StableHash for i64 {
17 fn stable_hash(&self) -> u64 {
18 *self as u64
19 }
20}
21
22impl StableHash for u64 {
23 fn stable_hash(&self) -> u64 {
24 *self
25 }
26}
27
28impl StableHash for String {
29 fn stable_hash(&self) -> u64 {
30 self.as_bytes().stable_hash()
31 }
32}
33
34impl StableHash for &[u8] {
35 fn stable_hash(&self) -> u64 {
36 const CHUNK_SIZE: usize = std::mem::size_of::<u64>();
37 let chunks = self.len() / CHUNK_SIZE;
38 let remainder = self.len() % CHUNK_SIZE;
39 let mut hash = 0_u64;
40
41 for chunk in 0..chunks {
42 let begin = chunk * CHUNK_SIZE;
43 let end = begin + CHUNK_SIZE;
44 let mut data = [0_u8; CHUNK_SIZE];
45 data.copy_from_slice(&self[begin..end]);
46 Self::add_to_hash(&mut hash, u64::from_le_bytes(data).stable_hash());
47 }
48
49 if remainder != 0 {
50 let begin = chunks * CHUNK_SIZE;
51 let end = begin + remainder;
52 let mut data = [0_u8; CHUNK_SIZE];
53 data[0..remainder].copy_from_slice(&self[begin..end]);
54 Self::add_to_hash(&mut hash, u64::from_le_bytes(data).stable_hash());
55 }
56
57 Self::add_to_hash(&mut hash, (self.len() as u64).stable_hash());
58
59 hash
60 }
61}
62
63impl StableHash for Vec<u8> {
64 fn stable_hash(&self) -> u64 {
65 self.as_slice().stable_hash()
66 }
67}
68
69impl<T: StableHash> StableHash for Vec<T> {
70 fn stable_hash(&self) -> u64 {
71 let mut hash = 0_u64;
72
73 for value in self {
74 Self::add_to_hash(&mut hash, value.stable_hash());
75 }
76
77 Self::add_to_hash(&mut hash, (self.len() as u64).stable_hash());
78
79 hash
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn i64() {
89 assert_eq!(10_i64.stable_hash(), 10_u64);
90 }
91
92 #[test]
93 fn u64() {
94 assert_eq!(10_u64.stable_hash(), 10_u64);
95 }
96
97 #[test]
98 fn string() {
99 assert_eq!("".to_string().stable_hash(), 0);
100
101 let string_hash = "Hello, World!".to_string().stable_hash();
102 let other_string_hash = "Hello".to_string().stable_hash();
103
104 assert_ne!(string_hash, 0);
105 assert_ne!(" ".to_string().stable_hash(), 0);
106 assert_ne!(string_hash, other_string_hash);
107 assert_eq!(string_hash, "Hello, World!".to_string().stable_hash());
108 }
109
110 #[test]
111 fn vec_u8() {
112 let vec_hash = vec![1_u8, 2_u8, 3_u8].stable_hash();
113 let other_vec_hash = vec![3_u8, 2_u8, 1_u8].stable_hash();
114
115 assert_ne!(vec_hash, 0);
116 assert_ne!(vec![0_u8].stable_hash(), 0);
117 assert_ne!(vec_hash, other_vec_hash);
118 assert_eq!(vec_hash, vec![1_u8, 2_u8, 3_u8].stable_hash());
119 }
120
121 #[test]
122 fn vec_64() {
123 let vec_hash = vec![1_u64, 2_u64, 3_u64].stable_hash();
124 let other_vec_hash = vec![1_i64, 2_i64, 3_i64].stable_hash();
125
126 assert_ne!(vec_hash, 0);
127 assert_ne!(vec![0_u64].stable_hash(), 0);
128 assert_eq!(vec_hash, other_vec_hash);
129 assert_eq!(vec_hash, vec![1_u64, 2_u64, 3_u64].stable_hash());
130 }
131}