use std::collections::HashSet;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
pub struct StringPool {
pool: Arc<Mutex<HashSet<Arc<str>>>>,
}
impl StringPool {
#[must_use]
pub fn new() -> Self {
Self {
pool: Arc::new(Mutex::new(HashSet::new())),
}
}
#[must_use]
pub fn intern(&self, s: &str) -> Arc<str> {
if let Ok(mut pool) = self.pool.lock() {
if let Some(existing) = pool.get(s) {
return Arc::clone(existing);
}
let arc_str: Arc<str> = Arc::from(s);
pool.insert(Arc::clone(&arc_str));
arc_str
} else {
Arc::from(s)
}
}
#[must_use]
pub fn size(&self) -> usize {
self.pool.lock().map_or(0, |p| p.len())
}
pub fn clear(&self) {
if let Ok(mut pool) = self.pool.lock() {
pool.clear();
}
}
}
impl Default for StringPool {
fn default() -> Self {
Self::new()
}
}
fn global_pool() -> &'static StringPool {
static POOL: std::sync::OnceLock<StringPool> = std::sync::OnceLock::new();
POOL.get_or_init(StringPool::new)
}
#[must_use]
pub fn intern(s: &str) -> Arc<str> {
global_pool().intern(s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_interning() {
let pool = StringPool::new();
let s1 = pool.intern("hello");
let s2 = pool.intern("hello");
let s3 = pool.intern("world");
assert!(Arc::ptr_eq(&s1, &s2));
assert!(!Arc::ptr_eq(&s1, &s3));
assert_eq!(pool.size(), 2);
}
#[test]
fn test_global_pool() {
let s1 = intern("global");
let s2 = intern("global");
assert!(Arc::ptr_eq(&s1, &s2));
}
#[test]
fn test_clear() {
let pool = StringPool::new();
let _ = pool.intern("test1");
let _ = pool.intern("test2");
assert_eq!(pool.size(), 2);
pool.clear();
assert_eq!(pool.size(), 0);
}
}