1use std::collections::HashSet;
8use std::sync::{Arc, Mutex};
9
10#[derive(Clone)]
15pub struct StringPool {
16 pool: Arc<Mutex<HashSet<Arc<str>>>>,
17}
18
19impl StringPool {
20 #[must_use]
22 pub fn new() -> Self {
23 Self {
24 pool: Arc::new(Mutex::new(HashSet::new())),
25 }
26 }
27
28 #[must_use]
41 pub fn intern(&self, s: &str) -> Arc<str> {
42 if let Ok(mut pool) = self.pool.lock() {
43 if let Some(existing) = pool.get(s) {
45 return Arc::clone(existing);
46 }
47
48 let arc_str: Arc<str> = Arc::from(s);
50 pool.insert(Arc::clone(&arc_str));
51 arc_str
52 } else {
53 Arc::from(s)
55 }
56 }
57
58 #[must_use]
60 pub fn size(&self) -> usize {
61 self.pool.lock().map_or(0, |p| p.len())
62 }
63
64 pub fn clear(&self) {
66 if let Ok(mut pool) = self.pool.lock() {
67 pool.clear();
68 }
69 }
70}
71
72impl Default for StringPool {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78fn global_pool() -> &'static StringPool {
82 static POOL: std::sync::OnceLock<StringPool> = std::sync::OnceLock::new();
83 POOL.get_or_init(StringPool::new)
84}
85
86#[must_use]
88pub fn intern(s: &str) -> Arc<str> {
89 global_pool().intern(s)
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_string_interning() {
98 let pool = StringPool::new();
99
100 let s1 = pool.intern("hello");
101 let s2 = pool.intern("hello");
102 let s3 = pool.intern("world");
103
104 assert!(Arc::ptr_eq(&s1, &s2));
106 assert!(!Arc::ptr_eq(&s1, &s3));
108
109 assert_eq!(pool.size(), 2);
110 }
111
112 #[test]
113 fn test_global_pool() {
114 let s1 = intern("global");
115 let s2 = intern("global");
116
117 assert!(Arc::ptr_eq(&s1, &s2));
118 }
119
120 #[test]
121 fn test_clear() {
122 let pool = StringPool::new();
123 let _ = pool.intern("test1");
124 let _ = pool.intern("test2");
125 assert_eq!(pool.size(), 2);
126
127 pool.clear();
128 assert_eq!(pool.size(), 0);
129 }
130}