petty_intern/
sync.rs

1use {
2    rustc_hash::FxBuildHasher,
3    std::{
4        borrow::Borrow,
5        fmt,
6        hash::{BuildHasher, Hash},
7        sync::RwLock,
8    },
9};
10
11pub struct Interner<T> {
12    inner: RwLock<crate::Interner<T>>,
13}
14
15impl<T> Default for Interner<T> {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21impl<T> Interner<T> {
22    /// Creates an empty Interner.
23    /// The current implementation does not allocate
24    #[must_use]
25    pub const fn new() -> Self {
26        Self { inner: RwLock::new(crate::Interner::new()) }
27    }
28
29    /// Returns the number of entries in the interner
30    #[expect(clippy::missing_panics_doc)]
31    pub fn len(&self) -> usize {
32        self.inner.read().unwrap().len()
33    }
34
35    /// Returns `true` if the interner contains no elements
36    pub fn is_empty(&self) -> bool {
37        self.len() == 0
38    }
39
40    // Inserts the value into the interner's arena without checking if the value already exists.
41    // Future calls to intern will not find the same value, use `intern_new` if you want that behaviour.
42    pub fn insert_arena(&self, value: T) -> &mut T {
43        let inner = self.inner.write().unwrap();
44        unsafe { longer_mut(inner.insert_arena(value)) }
45    }
46}
47
48impl<T: Hash + Eq> Interner<T> {
49    /// Will return a reference to an equivalent value if it already exists
50    #[expect(clippy::missing_panics_doc)]
51    #[must_use]
52    pub fn try_resolve<Q>(&self, value: &Q) -> Option<&T>
53    where
54        T: Borrow<Q>,
55        Q: ?Sized + Hash + Eq,
56    {
57        self.inner.read().unwrap().try_resolve(value).map(|cached| unsafe { longer(cached) })
58    }
59
60    /// Returns a reference to either the value provided, or an equivalent value that was already inserted
61    #[expect(clippy::missing_panics_doc, clippy::readonly_write_lock)]
62    pub fn intern(&self, value: T) -> &T {
63        let hash = FxBuildHasher.hash_one(&value);
64
65        let inner = self.inner.read().unwrap();
66        if let Some(cached) = inner.try_resolve_with(&value, hash) {
67            return unsafe { longer(cached) };
68        }
69
70        drop(inner);
71        let inner = self.inner.write().unwrap();
72
73        // try again in case another thread inserted a value in between the drop(_) and the .writer().
74        // It would be nice to avoid the last 2 lookups if we could 'upgrade' the read guard and ask if there were any writes in between.
75        if let Some(cached) = inner.try_resolve_with(&value, hash) {
76            return unsafe { longer(cached) };
77        }
78
79        unsafe { longer(inner.insert(hash, value)) }
80    }
81
82    #[expect(clippy::missing_panics_doc, clippy::readonly_write_lock)]
83    /// Inserts the value into the interner without checking if the value already exists
84    pub fn intern_new(&self, value: T) -> &T {
85        let hash = FxBuildHasher.hash_one(&value);
86        let inner = self.inner.write().unwrap();
87        unsafe { longer(inner.insert(hash, value)) }
88    }
89
90    /// Inserts a reference to the value into the interner.
91    /// All future calls the `Interner::intern` will return the provided reference.
92    ///
93    /// This function will *not* check if an equivalent value already exists within the table,
94    /// and may behave weirdly if one does.
95    pub fn intern_ref_unique(&self, value: &'static T) {
96        self.inner.write().unwrap().intern_ref_unique(value)
97    }
98}
99
100impl<T: fmt::Debug> fmt::Debug for Interner<T> {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        let Ok(inner) = self.inner.try_read() else {
103            return f.debug_set().finish_non_exhaustive();
104        };
105        inner.fmt(f)
106    }
107}
108
109unsafe fn longer<'b, T>(short: &T) -> &'b T {
110    unsafe { std::mem::transmute(short) }
111}
112
113unsafe fn longer_mut<'b, T>(short: &mut T) -> &'b mut T {
114    unsafe { std::mem::transmute(short) }
115}
116
117unsafe impl<T: Send> Send for Interner<T> {}
118unsafe impl<T: Sync> Sync for Interner<T> {}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    #[test]
124    fn addr() {
125        static INTERNER: Interner<i32> = Interner::new();
126
127        let a1: *const _ = INTERNER.intern(1);
128        let b1: *const _ = INTERNER.intern(1);
129        INTERNER.intern(2);
130
131        assert!(INTERNER.try_resolve(&1) == Some(&1));
132
133        assert_eq!(a1.addr(), b1.addr());
134    }
135    #[test]
136    fn recursive() {
137        #[derive(Debug, PartialEq, Eq, Hash)]
138        enum Type<'tcx> {
139            Int,
140            Array(&'tcx Type<'tcx>),
141        }
142
143        let interner = Interner::new();
144        let int = interner.intern(Type::Int);
145        let array = interner.intern(Type::Array(int));
146        println!("{array:?}");
147    }
148    #[test]
149    fn insert_arena() {
150        let interner = Interner::new();
151        let a1: *const _ = interner.insert_arena(1);
152        let a2: *const _ = interner.intern(1);
153        assert_ne!(a1.addr(), a2.addr());
154    }
155    #[test]
156    fn test_grow() {
157        let interner = Interner::new();
158        let one: *const _ = interner.intern(1);
159
160        // force a grow
161        for i in 2..1000 {
162            interner.intern(i);
163        }
164
165        let one2: *const _ = interner.intern(1);
166        assert_eq!(one.addr(), one2.addr())
167    }
168    #[test]
169    fn test_intern_ref_unique() {
170        let interner = Interner::new();
171
172        static ONE: i32 = 1;
173        let a1 = &ONE;
174
175        interner.intern_ref_unique(a1);
176        let a2: *const _ = interner.intern(1);
177        assert_eq!((a1 as *const i32).addr(), a2.addr());
178    }
179}