bevy_ecs/
intern.rs

1//! Provides types used to statically intern immutable values.
2//!
3//! Interning is a pattern used to save memory by deduplicating identical values,
4//! speed up code by shrinking the stack size of large types,
5//! and make comparisons for any type as fast as integers.
6
7use core::{fmt::Debug, hash::Hash, ops::Deref};
8use std::sync::{OnceLock, PoisonError, RwLock};
9
10use bevy_utils::HashSet;
11
12/// An interned value. Will stay valid until the end of the program and will not drop.
13///
14/// For details on interning, see [the module level docs](self).
15///
16/// # Comparisons
17///
18/// Interned values use reference equality, meaning they implement [`Eq`]
19/// and [`Hash`] regardless of whether `T` implements these traits.
20/// Two interned values are only guaranteed to compare equal if they were interned using
21/// the same [`Interner`] instance.
22// NOTE: This type must NEVER implement Borrow since it does not obey that trait's invariants.
23/// ```
24/// # use bevy_ecs::intern::*;
25/// #[derive(PartialEq, Eq, Hash, Debug)]
26/// struct Value(i32);
27/// impl Internable for Value {
28///     // ...
29/// # fn leak(&self) -> &'static Self { Box::leak(Box::new(Value(self.0))) }
30/// # fn ref_eq(&self, other: &Self) -> bool { std::ptr::eq(self, other ) }
31/// # fn ref_hash<H: std::hash::Hasher>(&self, state: &mut H) { std::ptr::hash(self, state); }
32/// }
33/// let interner_1 = Interner::new();
34/// let interner_2 = Interner::new();
35/// // Even though both values are identical, their interned forms do not
36/// // compare equal as they use different interner instances.
37/// assert_ne!(interner_1.intern(&Value(42)), interner_2.intern(&Value(42)));
38/// ```
39pub struct Interned<T: ?Sized + 'static>(pub &'static T);
40
41impl<T: ?Sized> Deref for Interned<T> {
42    type Target = T;
43
44    fn deref(&self) -> &Self::Target {
45        self.0
46    }
47}
48
49impl<T: ?Sized> Clone for Interned<T> {
50    fn clone(&self) -> Self {
51        *self
52    }
53}
54
55impl<T: ?Sized> Copy for Interned<T> {}
56
57// Two Interned<T> should only be equal if they are clones from the same instance.
58// Therefore, we only use the pointer to determine equality.
59impl<T: ?Sized + Internable> PartialEq for Interned<T> {
60    fn eq(&self, other: &Self) -> bool {
61        self.0.ref_eq(other.0)
62    }
63}
64
65impl<T: ?Sized + Internable> Eq for Interned<T> {}
66
67// Important: This must be kept in sync with the PartialEq/Eq implementation
68impl<T: ?Sized + Internable> Hash for Interned<T> {
69    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
70        self.0.ref_hash(state);
71    }
72}
73
74impl<T: ?Sized + Debug> Debug for Interned<T> {
75    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
76        self.0.fmt(f)
77    }
78}
79
80impl<T> From<&Interned<T>> for Interned<T> {
81    fn from(value: &Interned<T>) -> Self {
82        *value
83    }
84}
85
86/// A trait for internable values.
87///
88/// This is used by [`Interner<T>`] to create static references for values that are interned.
89pub trait Internable: Hash + Eq {
90    /// Creates a static reference to `self`, possibly leaking memory.
91    fn leak(&self) -> &'static Self;
92
93    /// Returns `true` if the two references point to the same value.
94    fn ref_eq(&self, other: &Self) -> bool;
95
96    /// Feeds the reference to the hasher.
97    fn ref_hash<H: core::hash::Hasher>(&self, state: &mut H);
98}
99
100impl Internable for str {
101    fn leak(&self) -> &'static Self {
102        let str = self.to_owned().into_boxed_str();
103        Box::leak(str)
104    }
105
106    fn ref_eq(&self, other: &Self) -> bool {
107        self.as_ptr() == other.as_ptr() && self.len() == other.len()
108    }
109
110    fn ref_hash<H: core::hash::Hasher>(&self, state: &mut H) {
111        self.len().hash(state);
112        self.as_ptr().hash(state);
113    }
114}
115
116/// A thread-safe interner which can be used to create [`Interned<T>`] from `&T`
117///
118/// For details on interning, see [the module level docs](self).
119///
120/// The implementation ensures that two equal values return two equal [`Interned<T>`] values.
121///
122/// To use an [`Interner<T>`], `T` must implement [`Internable`].
123pub struct Interner<T: ?Sized + 'static>(OnceLock<RwLock<HashSet<&'static T>>>);
124
125impl<T: ?Sized> Interner<T> {
126    /// Creates a new empty interner
127    pub const fn new() -> Self {
128        Self(OnceLock::new())
129    }
130}
131
132impl<T: Internable + ?Sized> Interner<T> {
133    /// Return the [`Interned<T>`] corresponding to `value`.
134    ///
135    /// If it is called the first time for `value`, it will possibly leak the value and return an
136    /// [`Interned<T>`] using the obtained static reference. Subsequent calls for the same `value`
137    /// will return [`Interned<T>`] using the same static reference.
138    pub fn intern(&self, value: &T) -> Interned<T> {
139        let lock = self.0.get_or_init(Default::default);
140        {
141            let set = lock.read().unwrap_or_else(PoisonError::into_inner);
142            if let Some(value) = set.get(value) {
143                return Interned(*value);
144            }
145        }
146        {
147            let mut set = lock.write().unwrap_or_else(PoisonError::into_inner);
148            if let Some(value) = set.get(value) {
149                Interned(*value)
150            } else {
151                let leaked = value.leak();
152                set.insert(leaked);
153                Interned(leaked)
154            }
155        }
156    }
157}
158
159impl<T: ?Sized> Default for Interner<T> {
160    fn default() -> Self {
161        Self::new()
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use core::hash::{Hash, Hasher};
168    use std::collections::hash_map::DefaultHasher;
169
170    use crate::intern::{Internable, Interned, Interner};
171
172    #[test]
173    fn zero_sized_type() {
174        #[derive(PartialEq, Eq, Hash, Debug)]
175        pub struct A;
176
177        impl Internable for A {
178            fn leak(&self) -> &'static Self {
179                &A
180            }
181
182            fn ref_eq(&self, other: &Self) -> bool {
183                core::ptr::eq(self, other)
184            }
185
186            fn ref_hash<H: Hasher>(&self, state: &mut H) {
187                core::ptr::hash(self, state);
188            }
189        }
190
191        let interner = Interner::default();
192        let x = interner.intern(&A);
193        let y = interner.intern(&A);
194        assert_eq!(x, y);
195    }
196
197    #[test]
198    fn fieldless_enum() {
199        #[derive(PartialEq, Eq, Hash, Debug, Clone)]
200        pub enum A {
201            X,
202            Y,
203        }
204
205        impl Internable for A {
206            fn leak(&self) -> &'static Self {
207                match self {
208                    A::X => &A::X,
209                    A::Y => &A::Y,
210                }
211            }
212
213            fn ref_eq(&self, other: &Self) -> bool {
214                core::ptr::eq(self, other)
215            }
216
217            fn ref_hash<H: Hasher>(&self, state: &mut H) {
218                core::ptr::hash(self, state);
219            }
220        }
221
222        let interner = Interner::default();
223        let x = interner.intern(&A::X);
224        let y = interner.intern(&A::Y);
225        assert_ne!(x, y);
226    }
227
228    #[test]
229    fn static_sub_strings() {
230        let str = "ABC ABC";
231        let a = &str[0..3];
232        let b = &str[4..7];
233        // Same contents
234        assert_eq!(a, b);
235        let x = Interned(a);
236        let y = Interned(b);
237        // Different pointers
238        assert_ne!(x, y);
239        let interner = Interner::default();
240        let x = interner.intern(a);
241        let y = interner.intern(b);
242        // Same pointers returned by interner
243        assert_eq!(x, y);
244    }
245
246    #[test]
247    fn same_interned_instance() {
248        let a = Interned("A");
249        let b = a;
250
251        assert_eq!(a, b);
252
253        let mut hasher = DefaultHasher::default();
254        a.hash(&mut hasher);
255        let hash_a = hasher.finish();
256
257        let mut hasher = DefaultHasher::default();
258        b.hash(&mut hasher);
259        let hash_b = hasher.finish();
260
261        assert_eq!(hash_a, hash_b);
262    }
263
264    #[test]
265    fn same_interned_content() {
266        let a = Interned::<str>(Box::leak(Box::new("A".to_string())));
267        let b = Interned::<str>(Box::leak(Box::new("A".to_string())));
268
269        assert_ne!(a, b);
270    }
271
272    #[test]
273    fn different_interned_content() {
274        let a = Interned::<str>("A");
275        let b = Interned::<str>("B");
276
277        assert_ne!(a, b);
278    }
279}