use crate::types::InternedString;
use parking_lot::RwLock;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
#[inline]
fn hash_str(s: &str) -> u64 {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
s.hash(&mut hasher);
hasher.finish()
}
pub struct StringInterner {
strings: RwLock<Vec<Box<str>>>,
map: RwLock<HashMap<u64, Vec<InternedString>>>,
}
impl StringInterner {
pub fn new() -> Self {
Self {
strings: RwLock::new(Vec::new()),
map: RwLock::new(HashMap::new()),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
strings: RwLock::new(Vec::with_capacity(capacity)),
map: RwLock::new(HashMap::with_capacity(capacity)),
}
}
pub fn intern(&self, s: &str) -> InternedString {
let h = hash_str(s);
{
let map = self.map.read();
if let Some(chain) = map.get(&h) {
let strings = self.strings.read();
for &id in chain {
if &*strings[id.0 as usize] == s {
return id;
}
}
}
}
let mut map = self.map.write();
let mut strings = self.strings.write();
if let Some(chain) = map.get(&h) {
for &id in chain {
if &*strings[id.0 as usize] == s {
return id;
}
}
}
let id = InternedString(strings.len() as u32);
strings.push(Box::from(s));
map.entry(h).or_default().push(id);
id
}
#[inline]
pub fn resolve(&self, id: InternedString) -> Option<&str> {
unsafe {
let strings = self.strings.read();
let s = strings.get(id.0 as usize)?;
Some(std::mem::transmute::<&str, &str>(&**s))
}
}
#[inline]
pub fn len(&self) -> usize {
self.strings.read().len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.strings.read().is_empty()
}
}
impl Default for StringInterner {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intern_resolve() {
let interner = StringInterner::new();
let id1 = interner.intern("foo");
let id2 = interner.intern("bar");
let id3 = interner.intern("foo");
assert_eq!(id1, id3);
assert_ne!(id1, id2);
assert_eq!(interner.resolve(id1), Some("foo"));
assert_eq!(interner.resolve(id2), Some("bar"));
}
#[test]
fn test_thread_safety() {
use std::sync::Arc;
use std::thread;
let interner = Arc::new(StringInterner::new());
let mut handles = vec![];
for _ in 0..10 {
let interner = Arc::clone(&interner);
let handle = thread::spawn(move || {
for i in 0..100 {
interner.intern(&format!("string_{}", i));
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(interner.len(), 100);
}
#[test]
fn test_with_capacity() {
let interner = StringInterner::with_capacity(1000);
assert_eq!(interner.len(), 0);
for i in 0..500 {
interner.intern(&format!("str_{}", i));
}
assert_eq!(interner.len(), 500);
}
#[test]
fn test_box_arena_intern_resolve() {
let interner = StringInterner::new();
let id = interner.intern("hello");
assert_eq!(interner.resolve(id), Some("hello"));
}
#[test]
fn test_box_arena_dedup() {
let interner = StringInterner::new();
let id1 = interner.intern("a");
let id2 = interner.intern("a");
assert_eq!(id1, id2);
}
#[test]
fn test_box_arena_distinct() {
let interner = StringInterner::new();
let id1 = interner.intern("a");
let id2 = interner.intern("b");
assert_ne!(id1, id2);
}
#[test]
fn test_no_arc_in_struct() {
let interner = StringInterner::new();
let type_name = std::any::type_name_of_val(&interner);
assert!(
!type_name.contains("Arc"),
"StringInterner should not contain Arc: {}",
type_name
);
}
#[test]
fn test_thread_safety_concurrent_intern() {
use std::sync::Arc;
use std::thread;
let interner = Arc::new(StringInterner::new());
let mut handles = vec![];
for t in 0..8 {
let interner = Arc::clone(&interner);
let handle = thread::spawn(move || {
for i in 0..1000 {
let s = format!("t{}_{}", t, i);
let id = interner.intern(&s);
assert_eq!(interner.resolve(id), Some(s.as_str()));
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(interner.len(), 8000);
}
#[test]
fn test_capacity_growth() {
let interner = StringInterner::new();
for i in 0..10000 {
let s = format!("string_{}", i);
let id = interner.intern(&s);
assert_eq!(interner.resolve(id), Some(s.as_str()));
}
assert_eq!(interner.len(), 10000);
}
}