1#[cfg(target_family = "wasm")]
2compile_error!(
3 "The `interned-string` crate cannot run on WebAssembly due to needing multiple threads."
4);
5
6use std::{fmt::Debug, ops::Deref};
7use storage::{IStringKey, ThreadLocalReader, SHARED_STORAGE, THREAD_LOCAL_READER};
8
9mod storage;
10
11#[derive(Eq, PartialEq, Ord, Hash)]
25pub struct IString {
26 pub(crate) key: IStringKey
27}
28
29impl From<String> for IString {
32 #[inline]
46 fn from(string: String) -> Self {
47 Self {
48 key: SHARED_STORAGE.insert_or_retain(string)
50 }
51 }
52}
53
54impl From<&str> for IString {
55 #[inline]
69 fn from(string: &str) -> Self {
70 Self {
71 key: SHARED_STORAGE.insert_or_retain(String::from(string))
73 }
74 }
75}
76
77impl Drop for IString {
78 #[inline]
79 fn drop(&mut self) {
80 THREAD_LOCAL_READER.with(|tl_reader| {
81 tl_reader.release(self);
82 });
83 }
84}
85
86impl Deref for IString {
87 type Target = str;
88
89 #[inline]
106 fn deref(&self) -> &Self::Target {
107 THREAD_LOCAL_READER.with(|reader: &ThreadLocalReader| {
108 reader.read(self)
109 })
110 }
111}
112
113impl AsRef<str> for IString {
114 #[inline]
126 fn as_ref(&self) -> &str {
127 THREAD_LOCAL_READER.with(|tl_reader: &ThreadLocalReader| {
128 tl_reader.read(self)
129 })
130 }
131}
132
133impl Clone for IString {
136 #[inline]
140 fn clone(&self) -> Self {
141 THREAD_LOCAL_READER.with(|reader: &ThreadLocalReader| {
142 reader.retain(self.key)
143 });
144
145 Self { key: self.key }
146 }
147}
148
149impl PartialOrd for IString {
150 #[inline]
151 fn lt(&self, other: &Self) -> bool {
152 self.deref().lt(other.deref())
153 }
154
155 #[inline]
156 fn le(&self, other: &Self) -> bool {
157 self.deref().le(other.deref())
158 }
159
160 #[inline]
161 fn gt(&self, other: &Self) -> bool {
162 self.deref().gt(other.deref())
163 }
164
165 #[inline]
166 fn ge(&self, other: &Self) -> bool {
167 self.deref().ge(other.deref())
168 }
169
170 #[inline]
171 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
172 self.deref().partial_cmp(other.deref())
173 }
174}
175
176impl Debug for IString {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 f.debug_tuple("IString")
179 .field(&self.deref())
180 .finish()
181 }
182}
183
184impl std::fmt::Display for IString {
185 #[inline]
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 f.write_str(self)
188 }
189}
190
191impl Default for IString {
192 #[inline]
194 fn default() -> Self {
195 Self::from(String::default())
196 }
197}
198
199pub trait Intern {
202 fn intern(self) -> IString where Self: Sized;
203}
204
205impl Intern for String {
206 #[inline]
220 fn intern(self) -> IString {
221 IString::from(self)
222 }
223}
224
225impl Intern for &str {
226 #[inline]
240 fn intern(self) -> IString {
241 IString::from(self)
242 }
243}
244
245impl IString {
248 pub fn collect_garbage_now() {
257 SHARED_STORAGE.writer.lock().unwrap().collect_garbage();
258 }
259}
260
261#[cfg(feature = "serde")]
262mod feature_serde {
263 use serde::{de::Visitor, Deserialize, Serialize};
264 use crate::IString;
265
266 impl Serialize for IString {
267 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
268 serializer.serialize_str(std::ops::Deref::deref(&self))
269 }
270 }
271
272 impl<'de> Deserialize<'de> for IString {
273 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
274 deserializer.deserialize_string(IStringVisitor)
275 }
276 }
277
278 struct IStringVisitor;
279
280 impl<'de> Visitor<'de> for IStringVisitor {
281 type Value = IString;
282
283 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
284 formatter.write_str("a string")
285 }
286
287 fn visit_string<E: serde::de::Error>(self, string: String) -> Result<Self::Value, E> {
288 Ok(IString::from(string))
290 }
291
292 fn visit_str<E: serde::de::Error>(self, slice: &str) -> Result<Self::Value, E> {
293 Ok(IString::from(slice))
295 }
296 }
297}
298
299#[cfg(test)]
302mod tests {
303 use std::{ops::Deref, sync::Mutex};
304 use radix_trie::TrieCommon;
305
306 use super::*;
307 use crate::storage::SHARED_STORAGE;
308
309 #[test]
310 fn it_creates_and_removes_1_string() {
311 with_exclusive_use_of_shared_storage(|| {
312 let my_istring1 = "hello".intern();
313 assert!(my_istring1.deref() == "hello");
314
315 assert_string_count_in_storage(1);
316 assert_string_is_stored_with_key("hello", my_istring1.key);
317
318 drop(my_istring1);
319
320 assert_string_count_in_storage(1);
321 assert_string_is_still_stored("hello");
322
323 let my_istring2 = "another".to_string().intern();
324 assert!(my_istring2.deref() == "another");
325
326 assert_string_count_in_storage(1);
327 assert_string_is_stored_with_key("another", my_istring2.key);
328 assert_string_is_not_stored("hello")
329 });
330 }
331
332 #[test]
333 fn it_creates_and_removes_1_shared_string() {
334 with_exclusive_use_of_shared_storage(|| {
335 let my_istring1 = IString::from("hello");
336 let my_istring2 = IString::from("hello");
337 assert!(my_istring1.deref() == "hello");
338 assert!(my_istring2.deref() == "hello");
339 assert!(my_istring1.key == my_istring2.key);
340
341 assert_string_count_in_storage(1);
342 assert_string_is_stored_with_key("hello", my_istring1.key);
343
344 drop(my_istring1);
345
346 assert_string_count_in_storage(1);
347 assert_string_is_stored_with_key("hello", my_istring2.key);
348
349 drop(my_istring2);
350
351 assert_string_count_in_storage(1);
352 assert_string_is_still_stored("hello");
353 });
354 }
355
356 #[test]
357 fn it_creates_and_removes_3_strings() {
358 with_exclusive_use_of_shared_storage(|| {
359 let my_istring1 = IString::from("hello");
360 let my_istring2 = IString::from("world");
361 let my_istring3 = IString::from("howdy");
362 assert!(my_istring1.deref() == "hello");
363 assert!(my_istring2.deref() == "world");
364 assert!(my_istring3.deref() == "howdy");
365 assert!(my_istring1.key != my_istring2.key);
366 assert!(my_istring2.key != my_istring3.key);
367
368 assert_string_count_in_storage(3);
369 assert_string_is_stored_with_key("hello", my_istring1.key);
370 assert_string_is_stored_with_key("world", my_istring2.key);
371 assert_string_is_stored_with_key("howdy", my_istring3.key);
372 assert_string_is_not_stored("hola");
373
374 drop(my_istring1);
375 drop(my_istring2);
376
377 assert_string_count_in_storage(3);
378 assert_string_is_still_stored("hello");
379 assert_string_is_still_stored("world");
380 assert_string_is_stored_with_key("howdy", my_istring3.key);
381 assert_string_is_not_stored("hola");
382
383 let my_istring1bis = IString::from("hello");
385 assert!(my_istring1bis.deref() == "hello");
386
387 assert_string_count_in_storage(3);
389 assert_string_is_stored_with_key("hello", my_istring1bis.key);
390 assert_string_is_stored_with_key("howdy", my_istring3.key);
391 assert_string_is_still_stored("world");
392
393 let my_istring4 = IString::from("another");
394 assert!(my_istring4.deref() == "another");
395
396 assert_string_is_stored_with_key("hello", my_istring1bis.key);
398 assert_string_is_stored_with_key("howdy", my_istring3.key);
399 assert_string_is_stored_with_key("another", my_istring4.key);
400 assert_string_is_not_stored("world");
401 assert_string_count_in_storage(3);
402 });
403 }
404
405 #[test]
406 fn it_is_send() {
407 fn assert_send<T: Send>() {}
408 assert_send::<IString>();
409 }
410
411 #[test]
412 fn it_is_sync() {
413 fn assert_sync<T: Sync>() {}
414 assert_sync::<IString>();
415 }
416
417 #[cfg(feature = "serde")]
418 #[test]
419 fn it_serializes() {
420 with_exclusive_use_of_shared_storage(|| {
421 use serde::Serialize;
422
423 #[derive(Serialize)]
424 struct ExampleDTO {
425 favorite_dish: IString
426 }
427
428 let dto = ExampleDTO { favorite_dish: "pasta".intern() };
429
430 assert_eq!(serde_json::to_string(&dto).unwrap(), "{\"favorite_dish\":\"pasta\"}");
431 });
432 }
433
434 #[cfg(feature = "serde")]
435 #[test]
436 fn it_deserializes() {
437 with_exclusive_use_of_shared_storage(|| {
438 use serde::Deserialize;
439
440 #[derive(Deserialize, PartialEq, Debug)]
441 struct ExampleDTO {
442 favorite_dish: IString
443 }
444
445 let input = "{\"favorite_dish\":\"pasta\"}";
446
447 let dto: Result<ExampleDTO, _> = serde_json::from_str(input);
448
449 assert_eq!(dto.unwrap(), ExampleDTO { favorite_dish: "pasta".into() });
450 });
451 }
452
453 fn assert_string_count_in_storage(count: usize) {
454 let guard = SHARED_STORAGE.read_handle.lock().unwrap();
455 let read_handle = guard.enter().unwrap();
456 assert_eq!(read_handle.map.len(), count);
457 assert_eq!(read_handle.trie.len(), count);
458 }
459
460 fn assert_string_is_still_stored(string: &str) {
461 let guard = SHARED_STORAGE.read_handle.lock().unwrap();
462 let read_handle = guard.enter().unwrap();
463 let key = read_handle.trie.get(&string.into());
464 if let Some(key) = key {
465 assert!(read_handle.map.get(&key).unwrap().inner.deref() == string);
466 } else {
467 assert!(false, "the string is not in the trie");
468 }
469 }
470
471 fn assert_string_is_stored_with_key(string: &str, key: u32) {
472 let guard = SHARED_STORAGE.read_handle.lock().unwrap();
473 let read_handle = guard.enter().unwrap();
474 assert!(read_handle.map.get(&key).unwrap().inner.deref() == string);
475 assert_eq!(read_handle.trie.get(&string.into()), Some(&key));
476 }
477
478 fn assert_string_is_not_stored(string: &str) {
479 let guard = SHARED_STORAGE.read_handle.lock().unwrap();
480 let read_handle = guard.enter().unwrap();
481 assert_eq!(read_handle.trie.get(&string.into()), None);
482 }
483
484 static SHARED_STORAGE_MUTEX: Mutex<()> = Mutex::new(());
485
486 fn with_exclusive_use_of_shared_storage(closure: fn()) {
487 let guard = SHARED_STORAGE_MUTEX.lock().expect("test lock is not poisoned");
488 closure();
489
490 let mut writer = SHARED_STORAGE.writer.lock().unwrap();
492 writer.drain_channel_ops();
493 writer.write_handle.append(storage::StringStorageOp::DropUnusedStrings);
494 writer.write_handle.publish();
495 drop(writer);
496 drop(guard);
497 }
498}