use std::collections::HashSet;
use std::rc::Rc;
pub type IStr = Rc<str>;
#[derive(Debug, Default, Clone)]
pub struct Interner {
strings: HashSet<Rc<str>>,
}
impl Interner {
pub fn new() -> Self {
Self::default()
}
pub fn intern(&mut self, s: &str) -> IStr {
if let Some(existing) = self.strings.get(s) {
Rc::clone(existing)
} else {
let rc: Rc<str> = Rc::from(s);
self.strings.insert(Rc::clone(&rc));
rc
}
}
pub fn intern_string(&mut self, s: String) -> IStr {
if let Some(existing) = self.strings.get(s.as_str()) {
Rc::clone(existing)
} else {
let rc: Rc<str> = Rc::from(s);
self.strings.insert(Rc::clone(&rc));
rc
}
}
pub fn get(&self, s: &str) -> Option<IStr> {
self.strings.get(s).cloned()
}
pub fn len(&self) -> usize {
self.strings.len()
}
pub fn is_empty(&self) -> bool {
self.strings.is_empty()
}
pub fn clear(&mut self) {
self.strings.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intern_returns_same_rc() {
let mut interner = Interner::new();
let a = interner.intern("hello");
let b = interner.intern("hello");
assert!(Rc::ptr_eq(&a, &b));
}
#[test]
fn test_intern_different_strings() {
let mut interner = Interner::new();
let a = interner.intern("hello");
let b = interner.intern("world");
assert!(!Rc::ptr_eq(&a, &b));
assert_eq!(&*a, "hello");
assert_eq!(&*b, "world");
}
#[test]
fn test_clone_is_cheap() {
let mut interner = Interner::new();
let a = interner.intern("test");
let b = a.clone(); assert!(Rc::ptr_eq(&a, &b));
assert_eq!(Rc::strong_count(&a), 3); }
#[test]
fn test_get_existing() {
let mut interner = Interner::new();
interner.intern("exists");
assert!(interner.get("exists").is_some());
assert!(interner.get("missing").is_none());
}
}