#![doc = include_str!("../README.md")]
mod any;
mod common;
mod dropless;
mod typed;
pub use any::{AnyArena, AnyInternSet, AnyInterner};
pub use common::{Interned, RawInterned, UnsafeLock};
pub use dropless::{Dropless, DroplessInternSet, DroplessInterner};
pub use typed::TypedArena;
use std::{
any::TypeId,
borrow,
collections::HashMap,
fmt::{self, Display},
hash::{BuildHasher, Hash},
};
pub struct Interner<S = fxhash::FxBuildHasher> {
pub anys: UnsafeLock<HashMap<TypeId, AnyInternSet, S>>,
pub dropless: DroplessInterner,
}
impl Interner {
pub fn new() -> Self {
Self::default()
}
}
impl<S: BuildHasher> Interner<S> {
pub fn intern_static<K: Hash + Eq + 'static>(&self, value: K) -> Interned<'_, K> {
self.with_any_set::<K, _, _>(|set| unsafe {
set.intern(value)
})
}
pub fn intern_static_with<'a, K, Q, F>(&'a self, key: &Q, make_value: F) -> Interned<'a, K>
where
K: borrow::Borrow<Q> + 'static,
Q: Hash + Eq + ?Sized,
F: FnOnce() -> K,
{
self.with_any_set::<K, _, _>(|set| unsafe {
set.intern_with(key, make_value)
})
}
pub fn get<K, Q>(&self, key: &Q) -> Option<Interned<'_, K>>
where
K: borrow::Borrow<Q> + 'static,
Q: Hash + Eq + ?Sized,
{
self.with_any_set::<K, _, _>(|set| unsafe {
set.get(key)
})
}
pub fn intern_dropless<K: Dropless + ?Sized>(&self, value: &K) -> Interned<'_, K> {
self.dropless.intern(value)
}
pub fn intern_formatted_str<K: Display + ?Sized>(
&self,
value: &K,
upper_size: usize,
) -> Result<Interned<'_, str>, fmt::Error> {
self.dropless.intern_formatted_str(value, upper_size)
}
pub fn get_dropless<K: Dropless + ?Sized>(&self, value: &K) -> Option<Interned<'_, K>> {
self.dropless.get(value)
}
pub fn len(&self) -> usize {
self.with_any_sets(|sets| sets.values().map(AnyInternSet::len).sum::<usize>())
+ self.dropless.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&mut self) {
self.with_any_sets(|sets| {
for set in sets.values_mut() {
set.clear();
}
});
self.dropless.clear();
}
fn with_any_set<'this, K, F, R>(&'this self, f: F) -> R
where
K: 'static,
F: FnOnce(&'this mut AnyInternSet) -> R,
R: 'this,
{
self.with_any_sets(|sets| {
let set = sets
.entry(TypeId::of::<K>())
.or_insert_with(|| AnyInternSet::of::<K>());
f(set)
})
}
fn with_any_sets<'this, F, R>(&self, f: F) -> R
where
F: FnOnce(&'this mut HashMap<TypeId, AnyInternSet, S>) -> R,
R: 'this,
S: 'this,
{
unsafe {
let sets = self.anys.lock().as_mut();
let ret = f(sets);
self.anys.unlock();
ret
}
}
}
impl<S: Default> Default for Interner<S> {
fn default() -> Self {
let anys = unsafe { UnsafeLock::new(HashMap::default()) };
Self {
anys,
dropless: DroplessInterner::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::{self, RawInterned};
use std::mem;
#[test]
#[rustfmt::skip]
fn test_interner_various_types() {
#[derive(PartialEq, Eq, Hash)] struct A(i32);
#[derive(PartialEq, Eq, Hash)] struct B(i32);
let interner = Interner::new();
let groups: [&[RawInterned]; _] = [
&[interner.intern_static(A(0)).erased_raw(), interner.intern_static(A(0)).erased_raw()],
&[interner.intern_static(A(1)).erased_raw()],
&[interner.intern_static(B(0)).erased_raw(), interner.intern_static(B(0)).erased_raw()],
&[interner.intern_static(B(1)).erased_raw()],
];
common::assert_group_addr_eq(&groups);
}
#[test]
fn test_fixed_memory_after_huge_number_of_interninig() {
const TEST_SIZE_IN_BYTES: isize = if cfg!(miri) {
10 * 1024
} else {
5 * 1024 * 1024
};
let interner = Interner::new();
let mut remain_bytes = TEST_SIZE_IN_BYTES;
let mut interned_usize = Vec::new();
for i in 0_usize.. {
if remain_bytes < 0 {
break;
}
let value = i;
remain_bytes -= size_of_val(&value) as isize;
let interned = interner.intern_static(value);
interned_usize.push(interned);
}
let mut remain_bytes = TEST_SIZE_IN_BYTES;
let mut interned_str = Vec::new();
for i in 0.. {
if remain_bytes < 0 {
break;
}
let value = i.to_string();
let value = value.as_str();
remain_bytes -= mem::size_of_val(value) as isize;
let interned = interner.intern_dropless(value);
interned_str.push(interned);
}
for (i, interned) in interned_usize.into_iter().enumerate() {
assert_eq!(i, *interned)
}
for (i, interned) in interned_str.into_iter().enumerate() {
assert_eq!(*i.to_string(), *interned);
}
}
}