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