ergot_base/
nash.rs

1use core::num::NonZeroU32;
2
3use const_fnv1a_hash::fnv1a_hash_32;
4
5#[derive(Debug, PartialEq, Clone, Copy)]
6#[repr(transparent)]
7pub struct NameHash {
8    hash: NonZeroU32,
9}
10
11impl NameHash {
12    pub const fn new(s: &str) -> Self {
13        // ensure that an empty string does NOT hash to zero
14        let _: () = const {
15            assert!(0 != fnv1a_hash_32(&[], None));
16        };
17
18        let hash32 = fnv1a_hash_32(s.as_bytes(), None);
19        match NonZeroU32::new(hash32) {
20            Some(hash) => Self { hash },
21            None => {
22                // If the hash comes to zero, instead use the len + first three characters
23                //
24                // We know the len is non-zero because we checked that an empty string above
25                // hashes to a non-zero value.
26                let len = if s.len() > 255 { 255 } else { s.len() } as u8;
27                let mut bytes = [len, 0, 0, 0];
28
29                // Awkward because const
30                match s.as_bytes() {
31                    [] => unreachable!(),
32                    [one] => {
33                        bytes[1] = *one;
34                        bytes[2] = *one;
35                        bytes[3] = *one;
36                    }
37                    [one, two] => {
38                        bytes[1] = *one;
39                        bytes[2] = *two;
40                        bytes[3] = *one;
41                    }
42                    [one, two, three] => {
43                        bytes[1] = *one;
44                        bytes[2] = *two;
45                        bytes[3] = *three;
46                    }
47                    [one, two, three, ..] => {
48                        bytes[1] = *one;
49                        bytes[2] = *two;
50                        bytes[3] = *three;
51                    }
52                }
53                let val = u32::from_le_bytes(bytes);
54
55                // Safety:
56                //
57                // We know the len is nonzero, which means at least one byte has nonzero
58                // contents.
59                let hash = unsafe { NonZeroU32::new_unchecked(val) };
60                Self { hash }
61            }
62        }
63    }
64
65    pub fn to_u32(&self) -> u32 {
66        self.hash.into()
67    }
68
69    pub fn from_u32(val: u32) -> Option<Self> {
70        let nz = NonZeroU32::new(val)?;
71        Some(Self { hash: nz })
72    }
73}
74
75#[cfg(test)]
76mod test {
77    use crate::nash::NameHash;
78
79    #[test]
80    fn transparent_works() {
81        assert_eq!(size_of::<NameHash>(), 4);
82        assert_eq!(size_of::<Option<NameHash>>(), 4);
83    }
84}