Skip to main content

intuicio_data/
type_hash.rs

1//! Cheap runtime type identity.
2//!
3//! See [`TypeHash`].
4use rustc_hash::FxHasher;
5use std::{
6    cmp::Ordering,
7    hash::{Hash, Hasher},
8};
9
10/// Runtime identity of a type, stored as a hash of its full type name.
11///
12/// Used instead of [`std::any::TypeId`] because it can also be built for
13/// types that only exist on the script side, from their name alone (see
14/// [`TypeHash::raw`]). Comparison, ordering and hashing all use the hash
15/// only, never the name.
16///
17/// ```
18/// # use intuicio_data::type_hash::TypeHash;
19/// assert_eq!(TypeHash::of::<i32>(), TypeHash::of::<i32>());
20/// assert_ne!(TypeHash::of::<i32>(), TypeHash::of::<f32>());
21/// ```
22#[derive(Debug, Copy, Clone)]
23pub struct TypeHash {
24    hash: u64,
25    #[cfg(feature = "typehash_debug_name")]
26    name: Option<&'static str>,
27}
28
29impl Default for TypeHash {
30    fn default() -> Self {
31        Self::INVALID
32    }
33}
34
35impl TypeHash {
36    /// Hash that no real type maps to, used as a null value.
37    ///
38    /// This is also what [`TypeHash::default`] returns.
39    pub const INVALID: Self = Self {
40        hash: 0,
41        #[cfg(feature = "typehash_debug_name")]
42        name: None,
43    };
44
45    /// Builds a hash from a type name given at runtime.
46    ///
47    /// # Safety
48    ///
49    /// The name must be the full, qualified name of the type, the same name
50    /// [`TypeHash::of`] builds its hash from. A different name makes two views
51    /// of one type compare as different types. Type-erased containers use that
52    /// comparison to decide if a cast is safe.
53    pub unsafe fn raw(name: &str) -> Self {
54        let mut hasher = FxHasher::default();
55        name.hash(&mut hasher);
56        Self {
57            hash: hasher.finish(),
58            #[cfg(feature = "typehash_debug_name")]
59            name: None,
60        }
61    }
62
63    /// Same as [`TypeHash::raw`], but keeps the name for diagnostics when the
64    /// `typehash_debug_name` feature is on.
65    ///
66    /// # Safety
67    ///
68    /// Same as [`TypeHash::raw`].
69    pub unsafe fn raw_static(name: &'static str) -> Self {
70        let mut hasher = FxHasher::default();
71        name.hash(&mut hasher);
72        Self {
73            hash: hasher.finish(),
74            #[cfg(feature = "typehash_debug_name")]
75            name: Some(name),
76        }
77    }
78
79    /// Builds the hash of a type that exists only on the script side, from its
80    /// qualified name.
81    ///
82    /// The name is salted with NUL bytes, which [`std::any::type_name`] never
83    /// produces. So a runtime type never gets the same hash as a Rust type, and
84    /// a runtime value never passes the check that reads it as a Rust type.
85    /// Reflection stays the only way to read such a value.
86    ///
87    /// This is safe, unlike [`TypeHash::raw`], because the hash it builds
88    /// cannot match a Rust type.
89    ///
90    /// ```
91    /// # use intuicio_data::type_hash::TypeHash;
92    /// assert_eq!(
93    ///     TypeHash::of_runtime("game::Player"),
94    ///     TypeHash::of_runtime("game::Player")
95    /// );
96    /// assert_ne!(
97    ///     TypeHash::of_runtime("game::Player"),
98    ///     TypeHash::of_runtime("game::Vector")
99    /// );
100    /// assert_ne!(TypeHash::of_runtime("i32"), TypeHash::of::<i32>());
101    /// ```
102    pub fn of_runtime(qualified_name: &str) -> Self {
103        const SALT: &str = "\0intuicio::runtime\0";
104        let mut hasher = FxHasher::default();
105        SALT.hash(&mut hasher);
106        qualified_name.hash(&mut hasher);
107        Self {
108            hash: hasher.finish(),
109            #[cfg(feature = "typehash_debug_name")]
110            name: None,
111        }
112    }
113
114    /// Builds the hash of a Rust type known at compile time.
115    pub fn of<T: ?Sized>() -> Self {
116        let name = std::any::type_name::<T>();
117        let mut hasher = FxHasher::default();
118        name.hash(&mut hasher);
119        Self {
120            hash: hasher.finish(),
121            #[cfg(feature = "typehash_debug_name")]
122            name: Some(name),
123        }
124    }
125
126    /// Returns `false` only for [`TypeHash::INVALID`].
127    pub fn is_valid(&self) -> bool {
128        self.hash != Self::INVALID.hash
129    }
130
131    /// Returns the raw hash value.
132    pub fn hash(&self) -> u64 {
133        self.hash
134    }
135
136    /// Returns the type name this hash was built from, when it is known.
137    ///
138    /// Only available with the `typehash_debug_name` feature.
139    #[cfg(feature = "typehash_debug_name")]
140    pub fn name(&self) -> Option<&'static str> {
141        self.name
142    }
143}
144
145impl PartialEq for TypeHash {
146    fn eq(&self, other: &Self) -> bool {
147        self.hash == other.hash
148    }
149}
150
151impl Eq for TypeHash {}
152
153impl PartialOrd for TypeHash {
154    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
155        Some(self.cmp(other))
156    }
157}
158
159impl Ord for TypeHash {
160    fn cmp(&self, other: &Self) -> Ordering {
161        self.hash.cmp(&other.hash)
162    }
163}
164
165impl Hash for TypeHash {
166    fn hash<H: Hasher>(&self, state: &mut H) {
167        self.hash.hash(state);
168    }
169}
170
171impl std::fmt::Display for TypeHash {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        #[cfg(feature = "typehash_debug_name")]
174        {
175            if let Some(name) = self.name {
176                return write!(f, "#{:X}: {}", self.hash, name);
177            }
178        }
179        write!(f, "#{:X}", self.hash)
180    }
181}