interning/
hash.rs

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}