interner/lib.rs
1//! String interner
2//!
3//! Interning strings gives us two benefits.
4//!
5//! 1) Less memory usage, since we don't need to over-allocate memory.
6//! If we intern "abcd" 1000 times, we will only need 4 bytes.
7//! Compared with using 1000 [String]s, which would cause 1000 allocations
8//!
9//! 2) Faster comparison.
10//! Instead of comparing string slices, which takes a lot of time, comparing
11//! symbols (which are represented as an usize) is much more faster.
12//! Since all calls to [`Symbol::new`] with the same string will resolve to the
13//! same [Symbol], we can just compare the symbols.
14//!
15//! # Example
16//! ```
17//! use interner::Symbol;
18//!
19//! let str1 = Symbol::new("abcdef");
20//! let same = Symbol::new("abcdef");
21//! assert_eq!(str1, same);
22//!
23//! str1.borrow(|s| assert_eq!(s, "abcdef"));
24//! ```
25
26use core::fmt::{Display, Debug};
27use core::str::FromStr;
28use std::sync::{LazyLock, RwLock};
29
30use interns::StringInterner;
31
32#[derive(Clone, Copy, Hash, Eq, PartialEq)]
33#[repr(transparent)]
34/// Identifies an interned string.
35pub struct Symbol(interns::Symbol<str>);
36
37impl Symbol {
38 /// Gets a symbol from the given string.
39 ///
40 /// # Example
41 /// ```
42 /// use interner::Symbol;
43 ///
44 /// let kw_const = Symbol::new("const");
45 /// kw_const.borrow(|s| assert_eq!(s, "const"));
46 /// ```
47 #[inline]
48 pub fn new(s: &str) -> Self {
49 GLOBAL_INTERNER.get_or_intern(s)
50 }
51
52 /// Borrows this symbol from the global interner, and applies
53 /// the given closure to it
54 ///
55 /// # Example
56 /// ```
57 /// use interner::Symbol;
58 ///
59 /// let sym = Symbol::new("my string");
60 /// sym.borrow(|s| {
61 /// println!("Symbol {sym:?} resolved to '{s}'");
62 /// });
63 /// ```
64 #[inline]
65 pub fn borrow<R>(&self, f: impl FnOnce(&str) -> R) -> R {
66 GLOBAL_INTERNER.resolve_unchecked(*self, f)
67 }
68}
69
70impl FromStr for Symbol {
71 type Err = core::convert::Infallible;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 Ok(Self::new(s))
75 }
76}
77
78impl PartialEq<&str> for Symbol {
79 /// Attemps to resolve the symbol, and compares it
80 /// with the given string
81 ///
82 /// NOTE: If the symbol doesn't exist in the session
83 /// storage, it returns false.
84 fn eq(&self, other: &&str) -> bool {
85 GLOBAL_INTERNER.resolve_unchecked(*self, |s| s == *other)
86 }
87}
88
89impl Debug for Symbol {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 GLOBAL_INTERNER.resolve(*self, |sym| {
92 match sym {
93 Some(s) => write!(f, "{s}"),
94 None => write!(f, "{:?}", self.0),
95 }
96 })
97 }
98}
99
100impl Display for Symbol {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 GLOBAL_INTERNER.resolve_unchecked(*self, |s| {
103 write!(f, "{s}")
104 })
105 }
106}
107
108struct Interner(RwLock<StringInterner>);
109
110static GLOBAL_INTERNER: LazyLock<Interner> = LazyLock::new(|| Interner(RwLock::new(StringInterner::new())));
111
112impl Interner {
113 #[inline]
114 fn get_or_intern(&self, src: &str) -> Symbol {
115 Symbol(self.0.write().unwrap().get_or_intern(src))
116 }
117
118 #[inline]
119 fn resolve<R>(&self, sym: Symbol, f: impl FnOnce(Option<&str>) -> R) -> R {
120 f(self.0.read().unwrap().resolve(sym.0))
121 }
122
123 fn resolve_unchecked<R>(&self, sym: Symbol, f: impl FnOnce(&str) -> R) -> R {
124 let i = self.0.read().unwrap();
125 let s = i.resolve(sym.0).unwrap_or_else(|| {
126 /* It's VERY unlikely that we ask the interner for a
127 * symbol it hasn't generated. */
128 cold();
129 panic!("Attemp to get unexisting symbol: {sym:?}")
130 });
131 f(s)
132 }
133}
134
135#[inline]
136#[cold]
137fn cold() {}