ralph_workflow/checkpoint/
string_pool.rs1use std::collections::HashSet;
8use std::iter;
9use std::sync::Arc;
10
11#[derive(Debug, Clone, Default)]
30pub struct StringPool {
31 pool: HashSet<Arc<str>>,
34}
35
36impl StringPool {
37 #[must_use]
42 pub fn new() -> Self {
43 Self::with_capacity(16)
44 }
45
46 #[must_use]
51 pub fn with_capacity(capacity: usize) -> Self {
52 Self {
53 pool: HashSet::with_capacity(capacity),
54 }
55 }
56
57 #[must_use]
72 pub fn intern_str(self, s: &str) -> (Self, Arc<str>) {
73 if let Some(existing) = self.pool.get(s).map(Arc::clone) {
74 return (self, existing);
75 }
76
77 let interned: Arc<str> = Arc::from(s);
78 let pool = self
79 .pool
80 .into_iter()
81 .chain(iter::once(Arc::clone(&interned)))
82 .collect();
83 (Self { pool }, interned)
84 }
85
86 #[must_use]
90 pub fn intern_string(self, s: String) -> (Self, Arc<str>) {
91 if let Some(existing) = self.pool.get(s.as_str()).map(Arc::clone) {
92 return (self, existing);
93 }
94
95 let interned: Arc<str> = Arc::from(s);
96 let pool = self
97 .pool
98 .into_iter()
99 .chain(iter::once(Arc::clone(&interned)))
100 .collect();
101 (Self { pool }, interned)
102 }
103
104 pub fn intern(self, s: impl Into<String>) -> (Self, Arc<str>) {
109 self.intern_string(s.into())
110 }
111
112 #[must_use]
114 pub fn len(&self) -> usize {
115 self.pool.len()
116 }
117
118 #[must_use]
120 pub fn is_empty(&self) -> bool {
121 self.pool.is_empty()
122 }
123
124 #[must_use]
126 pub fn clear(self) -> Self {
127 Self::with_capacity(16)
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_string_pool_new() {
137 let pool = StringPool::new();
138 assert_eq!(pool.len(), 0);
139 assert!(pool.is_empty());
140 }
141
142 #[test]
143 fn test_string_pool_with_capacity() {
144 let pool = StringPool::with_capacity(32);
145 assert_eq!(pool.len(), 0);
146 assert!(pool.is_empty());
147 }
149
150 #[test]
151 fn test_identical_strings_return_same_arc() {
152 let (pool, s1) = StringPool::new().intern_str("Development");
153 let (pool, s2) = pool.intern_str("Development");
154
155 assert!(Arc::ptr_eq(&s1, &s2));
157 assert_eq!(*s1, *s2);
158 assert_eq!(pool.len(), 1);
159 }
160
161 #[test]
162 fn test_different_strings_return_different_arc() {
163 let (pool, s1) = StringPool::new().intern_str("Development");
164 let (pool, s2) = pool.intern_str("Review");
165
166 assert!(!Arc::ptr_eq(&s1, &s2));
168 assert_ne!(*s1, *s2);
169 assert_eq!(pool.len(), 2);
170 }
171
172 #[test]
173 fn test_pool_size_does_not_grow_for_repeated_strings() {
174 let pool = (0..100).fold(StringPool::new(), |pool, _| {
175 pool.intern_str("Development").0
176 });
177
178 assert_eq!(pool.len(), 1);
180 }
181
182 #[test]
183 fn test_intern_different_string_types() {
184 let (pool, s1) = StringPool::new().intern_str("test");
185
186 let (pool, s2) = pool.intern("test".to_string());
188 let (pool, s3) = pool.intern(String::from("test"));
189
190 assert!(Arc::ptr_eq(&s1, &s2));
192 assert!(Arc::ptr_eq(&s2, &s3));
193 assert_eq!(pool.len(), 1);
194 }
195
196 #[test]
197 fn test_intern_str_and_intern_string_share_entries() {
198 let (pool, s1) = StringPool::new().intern_str("test");
201
202 let (pool, s2) = pool.intern("test".to_string());
203 let (pool, s3) = pool.intern(String::from("test"));
204
205 assert!(Arc::ptr_eq(&s1, &s2));
206 assert!(Arc::ptr_eq(&s2, &s3));
207 assert_eq!(pool.len(), 1);
208 }
209
210 #[test]
211 fn test_clear() {
212 let pool = StringPool::new()
213 .intern_str("Development")
214 .0
215 .intern_str("Review")
216 .0;
217 assert_eq!(pool.len(), 2);
218
219 let pool = pool.clear();
220 assert_eq!(pool.len(), 0);
221 assert!(pool.is_empty());
222 }
223
224 #[test]
225 fn test_arc_content_matches_input() {
226 let arc = StringPool::new().intern_str("Development").1;
227 assert_eq!(&*arc, "Development");
228 }
229
230 #[test]
231 fn test_memory_efficiency_multiple_calls() {
232 let pool = (0..1000).fold(StringPool::new(), |pool, _| {
233 pool.intern_str("Development").0
234 });
235
236 assert_eq!(pool.len(), 1);
238
239 let arcs: Vec<_> = (0..1000)
241 .map(|_| pool.clone().intern_str("Development").1)
242 .collect();
243
244 assert!((1..arcs.len()).all(|i| Arc::ptr_eq(&arcs[0], &arcs[i])));
246 }
247
248 #[test]
249 fn test_empty_string() {
250 let (pool, s1) = StringPool::new().intern_str("");
251 let (pool, s2) = pool.intern_str("");
252
253 assert!(Arc::ptr_eq(&s1, &s2));
254 assert_eq!(&*s1, "");
255 assert_eq!(pool.len(), 1);
256 }
257
258 #[test]
259 fn test_clone_pool() {
260 let pool = StringPool::new()
261 .intern_str("Development")
262 .0
263 .intern_str("Review")
264 .0;
265
266 let cloned = pool.clone();
267 assert_eq!(pool.len(), cloned.len());
268 }
269}