1use stable_hash::fast_stable_hash;
2use std::fmt::{Debug, Display};
3use std::hash::Hash;
4
5pub const HASH_LEN_MASK: u8 = 0x80;
6
7#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Copy)]
8pub struct InternedStringHash([u8; 8]);
9impl Display for InternedStringHash {
10 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11 if self.is_inlined() {
12 write!(f, "{}", self.get_inlined_str())
13 } else {
14 write!(f, "{:x}", self.hash())
15 }
16 }
17}
18impl Debug for InternedStringHash {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 write!(f, "InternedStringHash({})", self)
21 }
22}
23impl InternedStringHash {
24 pub fn new(hash: u64) -> Self {
25 let mut bytes = [0; 8];
26 bytes.copy_from_slice(&hash.to_be_bytes());
27 Self(bytes)
28 }
29 pub fn from_str(s: &str) -> Self {
30 if s.len() >= 8 {
31 let mut hash = (fast_stable_hash(&s) as u64).to_be_bytes();
32 hash[0] = 0;
33 Self::from_bytes(hash)
34 } else {
35 let mut hash = [0u8; 8];
36 hash[0] = HASH_LEN_MASK | (s.len() as u8);
37 hash[1..=s.len()].copy_from_slice(s.as_bytes());
38 Self::from_bytes(hash)
39 }
40 }
41 pub fn empty() -> Self {
42 Self([0; 8])
43 }
44 pub fn from_bytes(bytes: [u8; 8]) -> Self {
45 Self(bytes)
46 }
47 pub fn hash(&self) -> u64 {
48 u64::from_be_bytes(self.0)
49 }
50 pub fn is_inlined(&self) -> bool {
51 self.0[0] != 0
52 }
53 pub fn set_inline_len(&mut self, len: usize) {
54 debug_assert!(len < 7);
55 self.0[0] = HASH_LEN_MASK | len as u8;
56 }
57 pub fn get_inlined_len(&self) -> usize {
58 debug_assert!(self.is_inlined());
59 (self.0[0] ^ HASH_LEN_MASK) as usize
60 }
61 pub fn get_inlined_str(&self) -> &str {
62 debug_assert!(self.is_inlined());
63 let len = self.get_inlined_len();
64 let ptr = self.0.as_ptr();
65 let ptr = unsafe { ptr.add(1) };
66 unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(ptr, len)) }
67 }
68}
69#[cfg(test)]
70mod tests {
71
72 use super::*;
73
74 #[test]
75 fn test_small_strings() {
76 let s = "hello";
77 let interned = InternedStringHash::from_str(s);
78 assert_eq!(interned.get_inlined_str(), s);
79 let interned = InternedStringHash::from_str(s);
80 assert_eq!(interned.get_inlined_str(), s);
81 }
82}