Skip to main content

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}