#![warn(missing_docs)]
use crate::{
parking_lot::Mutex,
visitor::{Visit, VisitResult, Visitor},
};
use fxhash::{FxHashMap, FxHasher};
use std::{
fmt::{Debug, Display, Formatter},
hash::{Hash, Hasher},
ops::Deref,
sync::Arc,
};
#[derive(Clone, Debug)]
struct State {
string: String,
hash: u64,
}
#[derive(Clone)]
pub struct ImmutableString(Arc<State>);
impl Display for ImmutableString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.string.as_ref())
}
}
impl Debug for ImmutableString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.0.string, f)
}
}
impl Visit for ImmutableString {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut string = self.0.string.clone();
string.visit(name, visitor)?;
if visitor.is_reading() {
*self = SSTORAGE.lock().insert(string);
}
Ok(())
}
}
impl Default for ImmutableString {
fn default() -> Self {
Self::new("")
}
}
impl ImmutableString {
#[inline]
pub fn new<S: AsRef<str>>(string: S) -> ImmutableString {
SSTORAGE.lock().insert(string)
}
#[inline]
pub fn id(&self) -> u64 {
self.0.hash
}
#[inline]
pub fn to_mutable(&self) -> String {
self.0.string.clone()
}
}
impl Deref for ImmutableString {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.0.string.as_ref()
}
}
impl Hash for ImmutableString {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.id())
}
}
impl PartialEq for ImmutableString {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl Eq for ImmutableString {}
#[derive(Default)]
pub struct ImmutableStringStorage {
vec: FxHashMap<u64, Arc<State>>,
}
impl ImmutableStringStorage {
#[inline]
fn insert<S: AsRef<str>>(&mut self, string: S) -> ImmutableString {
let mut hasher = FxHasher::default();
string.as_ref().hash(&mut hasher);
let hash = hasher.finish();
if let Some(existing) = self.vec.get(&hash) {
ImmutableString(existing.clone())
} else {
let immutable = Arc::new(State {
string: string.as_ref().to_owned(),
hash,
});
self.vec.insert(hash, immutable.clone());
ImmutableString(immutable)
}
}
}
impl ImmutableStringStorage {
pub fn entry_count() -> usize {
SSTORAGE.lock().vec.len()
}
}
lazy_static! {
static ref SSTORAGE: Arc<Mutex<ImmutableStringStorage>> =
Arc::new(Mutex::new(ImmutableStringStorage::default()));
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_immutable_string_uniqueness() {
let a = ImmutableString::new("Foobar");
let b = ImmutableString::new("Foobar");
assert_eq!(ImmutableStringStorage::entry_count(), 2);
assert_eq!(a.id(), b.id())
}
#[test]
fn visit_for_immutable_string() {
let mut a = ImmutableString::new("Foobar");
let mut visitor = Visitor::default();
assert!(a.visit("name", &mut visitor).is_ok());
}
#[test]
fn debug_for_immutable_string() {
let a = ImmutableString::new("Foobar");
assert_eq!(format!("{a:?}"), "\"Foobar\"");
}
#[test]
fn default_for_immutable_string() {
let a = ImmutableString::default();
assert_eq!(a.0.string, "");
}
#[test]
fn immutable_string_to_mutable() {
let a = ImmutableString::new("Foobar");
assert_eq!(a.to_mutable(), String::from("Foobar"));
}
#[test]
fn deref_for_immutable_string() {
let s = "Foobar";
let a = ImmutableString::new(s);
assert_eq!(a.deref(), s);
}
#[test]
fn eq_for_immutable_string() {
let a = ImmutableString::new("Foobar");
let b = ImmutableString::new("Foobar");
assert!(a == b);
}
}