hax_rust_engine/interning.rs
1//! # Interning System
2//!
3//! This module provides a minimal system for **global interning** of values in
4//! Rust. Interning allows you to deduplicate equal values and replace them with
5//! cheap, copyable handles (`Interned<T>`) that support **O(1) equality**,
6//! hashing, and compact storage.
7//!
8//! ## Core Concepts
9//!
10//! - [`Interned<T>`]: A compact, copyable handle to a deduplicated value.
11//! - [`InterningTable<T>`]: Stores interned values and manages uniqueness.
12//! - [`Internable`]: A trait for types that can be interned.
13//!
14//! ## Safety Note
15//!
16//! The `.get()` method on `Interned<T>` returns a `&'static T` using an
17//! internal `transmute`, assuming the backing storage (interning table) never
18//! remove items from its table. This is guaranteed by the implementation of
19//! `InterningTable`.
20
21use std::{
22 collections::{HashMap, HashSet},
23 fmt::Debug,
24 hash::Hash,
25 marker::PhantomData,
26 ops::Deref,
27 sync::{LazyLock, Mutex},
28};
29
30use schemars::JsonSchema;
31use serde::{Deserialize, Serialize};
32
33/// An interning table storing unique values of `T` and assigning them stable indices.
34///
35/// This type is primarily an implementation detail behind [`Interned<T>`] and
36/// the [`Internable`] trait. You typically won't use it directly unless you're
37/// wiring up a new globally‑interned type.
38pub struct InterningTable<T> {
39 /// The raw items: item at index `n` will be an `Interned { index: n }`.
40 /// Fast lookup.
41 items: Vec<T>,
42 /// A map from `T`s to indexes, for fast interning of existing values.
43 ids: HashMap<T, Interned<T>>,
44}
45
46impl<T> Default for InterningTable<T> {
47 fn default() -> Self {
48 Self {
49 items: Default::default(),
50 ids: Default::default(),
51 }
52 }
53}
54
55/// A statically interned value of type `T`.
56///
57/// An `Interned<T>` is a compact, copyable handle that deduplicates equal values
58/// and compares in **O(1)** using its index. It behaves like `&'static T` via
59/// [`Deref`], and can be obtained with [`InternExtTrait::intern`] or
60/// [`Interned::intern`].
61// Note: `Interned<T>` has `PartialEq` only if `T` has `PartialEq`. If we
62// implement `PartialEq` manually, we loose the ability to pattern match on
63// constant of this type. This is because of structural equality (see
64// https://doc.rust-lang.org/stable/std/marker/trait.StructuralPartialEq.html).
65#[derive(Hash, Eq, PartialEq)]
66pub struct Interned<T> {
67 phantom: PhantomData<T>,
68 index: u32,
69}
70
71impl<T: Eq> PartialOrd for Interned<T> {
72 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
73 Some(self.cmp(other))
74 }
75}
76impl<T: Eq> Ord for Interned<T> {
77 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
78 self.index.cmp(&other.index)
79 }
80}
81
82impl<T: Serialize + Internable> Serialize for Interned<T> {
83 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
84 where
85 S: serde::Serializer,
86 {
87 self.get().serialize(serializer)
88 }
89}
90
91impl<T: Internable> AsRef<T> for Interned<T> {
92 fn as_ref(&self) -> &T {
93 (*self).get()
94 }
95}
96
97impl<'a, T: Deserialize<'a> + Internable> Deserialize<'a> for Interned<T> {
98 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
99 where
100 D: serde::Deserializer<'a>,
101 {
102 Ok(Interned::intern(&T::deserialize(deserializer)?))
103 }
104}
105
106impl<T: JsonSchema> JsonSchema for Interned<T> {
107 fn schema_name() -> String {
108 T::schema_name()
109 }
110
111 fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
112 T::json_schema(generator)
113 }
114}
115
116impl<T: Internable + Debug> Debug for Interned<T> {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 f.debug_struct("Interned")
119 .field("index", &self.index)
120 .field("value", self.get())
121 .finish()
122 }
123}
124
125impl<T> Clone for Interned<T> {
126 fn clone(&self) -> Self {
127 *self
128 }
129}
130impl<T> Copy for Interned<T> {}
131
132/// A tiny, `FnOnce`-compatible wrapper used to initialize a `LazyLock` with a
133/// captured value.
134///
135/// This is a utility to build `LazyLock<T>` where the initializer needs to own
136/// some value prepared in a `const` context.
137///
138/// This is required since we need an explicit concrete type for the
139/// initializataion function given to `LazyLock::new`.
140///
141/// You usually don't need this directly unless you're calling
142/// [`InterningTable::new_with_values`].
143pub struct ExplicitClosure<T, R>(T, fn(T) -> R);
144impl<T, R> FnOnce<()> for ExplicitClosure<T, R> {
145 type Output = R;
146
147 extern "rust-call" fn call_once(self, _: ()) -> Self::Output {
148 let Self(input, function) = self;
149 function(input)
150 }
151}
152
153impl<T: Hash + Eq + Clone + Send> InterningTable<T> {
154 fn try_intern(&mut self, value: &T) -> Option<Interned<T>> {
155 Some(if let Some(interned) = self.ids.get(value) {
156 *interned
157 } else {
158 let index = self.items.len();
159 self.items.push(value.clone());
160 let handle = Interned {
161 phantom: PhantomData,
162 index: index.try_into().ok()?,
163 };
164 self.ids.insert(value.clone(), handle);
165 handle
166 })
167 }
168 fn get(&self, interned: Interned<T>) -> &T {
169 &self.items[interned.index as usize]
170 }
171
172 /// Creates a global `LazyLock` interning table prepopulated with `values`,
173 /// and returns both the lock and the corresponding `Interned<T>` handles.
174 ///
175 /// # Panics
176 ///
177 /// Panics if `values` contains duplicates (by `Eq`) or if `N` is greater
178 /// than `u32::MAX`.
179 pub const fn new_with_values<const N: usize>(
180 values: fn() -> [T; N],
181 ) -> (LazyLockNewWithValue<T, N>, [Interned<T>; N]) {
182 assert!(N < u32::MAX as usize);
183 let mut i = 0;
184 let mut interned_values: [Interned<T>; N] = [Interned {
185 phantom: PhantomData,
186 index: 0,
187 }; N];
188 while i < N {
189 interned_values[i].index = i as u32;
190 i += 1;
191 }
192 let lazy_lock = LazyLock::new(ExplicitClosure(values, |values| {
193 let values = values();
194 {
195 // Ensure `value` has no duplicate.
196 let set: HashSet<_> = values.iter().collect();
197 if set.len() != values.len() {
198 panic!("new_with_values: the input has duplicates");
199 }
200 }
201
202 let mut table = InterningTable::default();
203 for value in values {
204 if table.try_intern(&value).is_none() {
205 unreachable!(
206 "we asserted `N < u32::MAX`, the length of the internal vector `table` should be less than `u32::MAX`"
207 )
208 }
209 }
210 Mutex::new(table)
211 }));
212 (lazy_lock, interned_values)
213 }
214}
215
216/// A type alias representing a lazily initialized `Mutex<InterningTable<T>>`
217/// backed by a fixed-size array initializer.
218///
219/// This is the return type of [`InterningTable::new_with_values`].
220pub type LazyLockNewWithValue<T, const N: usize> =
221 LazyLock<Mutex<InterningTable<T>>, ExplicitClosure<fn() -> [T; N], Mutex<InterningTable<T>>>>;
222
223/// Types that have a single, process‑global interning table.
224///
225/// Implement this for your type to opt in to interning:
226/// provide a `static` (usually a `LazyLock<Mutex<InterningTable<Self>>>`)
227/// and return a reference to it.
228pub trait Internable: Sized + Hash + Eq + Clone + Send + 'static {
229 /// Returns the global interning table for `Self`.
230 fn interning_table() -> &'static Mutex<InterningTable<Self>>;
231
232 /// Interns a `value` and returns its compact handle.
233 ///
234 /// If an equal value has been interned before, this returns the existing
235 /// handle; otherwise it inserts the value into the global table.
236 fn intern(&self) -> Interned<Self> {
237 Interned::intern(self)
238 }
239}
240
241impl<T: Internable> Interned<T> {
242 /// Interns a `value` and returns its compact handle.
243 ///
244 /// If an equal value has been interned before, this returns the existing
245 /// handle; otherwise it inserts the value into the global table.
246 pub fn intern(value: &T) -> Self {
247 {
248 // Invariant: the interning mutex is only locked here, and InterningTable::try_intern
249 // is panic-free (and does not invoke user code that may panic). Therefore, no
250 // panic can occur while the mutex is held, so the mutex cannot be poisoned.
251 // If this ever panics, our invariant was broken elsewhere.
252 let mut table = T::interning_table()
253 .lock()
254 .expect("interning table mutex poisoned");
255 table.try_intern(value)
256 }
257 .unwrap_or_else(|| {
258 panic!(
259 "more than `u32::MAX` values have been interned for type `{}`",
260 std::any::type_name::<T>()
261 )
262 })
263 }
264
265 /// Returns a `&'static T` for this handle.
266 ///
267 /// # Safety & Lifetimes
268 ///
269 /// This method relies on the fact that the backing storage lives for the
270 /// entire program (it is kept in a `static` global table). The `'static`
271 /// reference is sound as long as values are never removed from that table.
272 /// This implementation uses `transmute` internally for that reason.
273 pub fn get(self) -> &'static T {
274 let table = T::interning_table().lock().unwrap();
275 let local_reference = table.get(self);
276 let static_reference: &'static T = unsafe { std::mem::transmute(local_reference) };
277 static_reference
278 }
279}
280
281impl<T: Internable> Deref for Interned<T> {
282 type Target = T;
283
284 /// Dereferences to the underlying value (`&'static T`).
285 ///
286 /// Equivalent to calling [`Interned::get`].
287 fn deref(&self) -> &Self::Target {
288 self.get()
289 }
290}