use std::{cell::RefCell, collections::HashSet}; /// A simple string pool. /// /// The typical use case for a [`StringPool`] is when dealing with values that /// borrow from their original text instead of making their own copies. /// /// By placing the source text into the string pool and deferring its cleanup /// until the [`StringPool`] is destroyed, you can avoid annoying lifetime /// issues or self-referential structs. #[derive(Debug, Default, Clone, PartialEq)] pub struct StringPool(RefCell>>); impl StringPool { pub fn empty() -> Self { StringPool::default() } /// Adds the text to the string pool, returning a reference which will live // as long as the [`StringPool`] itself. pub fn intern<'pool>(&'pool self, text: &str) -> &'pool str { let mut pool = self.0.borrow_mut(); let interned_string: &str = match pool.get(text) { Some(existing_value) => &existing_value, _ => { let boxed_copy: Box = text.into(); pool.insert(boxed_copy); &pool.get(text).unwrap() }, }; // SAFETY: by construction, it is safe to expand the string's // lifetime to that of the StringPool. // // While the Box may move around when our hash set gets resized, the // bytes making up the string will stay in the same place somewhere // on the heap. // // Additionally, once a string is added to the pool it can never be // removed. // // This means any &'pool pointers returned from this function will // be valid until the StringPool is dropped. unsafe { return std::mem::transmute(interned_string); } } }