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]
407 fn out_of_order_retain_release_does_not_panic() {
408 with_exclusive_use_of_shared_storage(|| {
409 let s1 = "hello".intern();
410 drop(s1); let s2 = "hello".intern();
414 drop(s2); let _ = "trigger_gc".intern();
418 });
419 }
420
421 #[test]
422 fn it_is_send() {
423 fn assert_send<T: Send>() {}
424 assert_send::<IString>();
425 }
426
427 #[test]
428 fn it_is_sync() {
429 fn assert_sync<T: Sync>() {}
430 assert_sync::<IString>();
431 }
432
433 #[cfg(feature = "serde")]
434 #[test]
435 fn it_serializes() {
436 with_exclusive_use_of_shared_storage(|| {
437 use serde::Serialize;
438
439 #[derive(Serialize)]
440 struct ExampleDTO {
441 favorite_dish: IString
442 }
443
444 let dto = ExampleDTO { favorite_dish: "pasta".intern() };
445
446 assert_eq!(serde_json::to_string(&dto).unwrap(), "{\"favorite_dish\":\"pasta\"}");
447 });
448 }
449
450 #[cfg(feature = "serde")]
451 #[test]
452 fn it_deserializes() {
453 with_exclusive_use_of_shared_storage(|| {
454 use serde::Deserialize;
455
456 #[derive(Deserialize, PartialEq, Debug)]
457 struct ExampleDTO {
458 favorite_dish: IString
459 }
460
461 let input = "{\"favorite_dish\":\"pasta\"}";
462
463 let dto: Result<ExampleDTO, _> = serde_json::from_str(input);
464
465 assert_eq!(dto.unwrap(), ExampleDTO { favorite_dish: "pasta".into() });
466 });
467 }
468
469 fn assert_string_count_in_storage(count: usize) {
470 let guard = SHARED_STORAGE.read_handle.lock().unwrap();
471 let read_handle = guard.enter().unwrap();
472 assert_eq!(read_handle.map.len(), count);
473 assert_eq!(read_handle.trie.len(), count);
474 }
475
476 fn assert_string_is_still_stored(string: &str) {
477 let guard = SHARED_STORAGE.read_handle.lock().unwrap();
478 let read_handle = guard.enter().unwrap();
479 let key = read_handle.trie.get(&string.into());
480 if let Some(key) = key {
481 assert!(read_handle.map.get(&key).unwrap().inner.deref() == string);
482 } else {
483 assert!(false, "the string is not in the trie");
484 }
485 }
486
487 fn assert_string_is_stored_with_key(string: &str, key: u32) {
488 let guard = SHARED_STORAGE.read_handle.lock().unwrap();
489 let read_handle = guard.enter().unwrap();
490 assert!(read_handle.map.get(&key).unwrap().inner.deref() == string);
491 assert_eq!(read_handle.trie.get(&string.into()), Some(&key));
492 }
493
494 fn assert_string_is_not_stored(string: &str) {
495 let guard = SHARED_STORAGE.read_handle.lock().unwrap();
496 let read_handle = guard.enter().unwrap();
497 assert_eq!(read_handle.trie.get(&string.into()), None);
498 }
499
500 static SHARED_STORAGE_MUTEX: Mutex<()> = Mutex::new(());
501
502 fn with_exclusive_use_of_shared_storage(closure: fn()) {
503 let guard = SHARED_STORAGE_MUTEX.lock().expect("test lock is not poisoned");
504 closure();
505
506 let mut writer = SHARED_STORAGE.writer.lock().unwrap();
508 writer.drain_channel_ops();
509 writer.write_handle.append(storage::StringStorageOp::DropUnusedStrings);
510 writer.write_handle.publish();
511 drop(writer);
512 drop(guard);
513 }
514}