#![warn(
unsafe_op_in_unsafe_fn,
clippy::missing_safety_doc,
clippy::multiple_unsafe_ops_per_block,
clippy::undocumented_unsafe_blocks
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use core::fmt;
use std::hash::{BuildHasher, Hash};
use std::num::NonZeroU32;
use std::ops::Index;
pub mod slice;
mod macros;
#[cfg(feature = "serde")]
mod serde;
#[cfg(not(feature = "serde"))]
mod serde {
#[doc(hidden)]
#[macro_export]
macro_rules! custom_key_serde {
($key:ident) => {};
}
pub use custom_key_serde;
}
#[doc(hidden)]
pub mod __private {
pub use foldhash::fast::RandomState;
pub mod serde {
pub use crate::serde::*;
}
}
custom_key!(
pub struct DefaultKey;
);
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)]
#[repr(transparent)]
pub struct Key(NonZeroU32);
impl std::fmt::Debug for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Key").field(&self.into_repr()).finish()
}
}
impl Key {
#[inline]
pub fn into_repr(self) -> u32 {
self.0.get() ^ u32::MAX
}
#[inline]
pub fn try_from_repr(x: u32) -> Option<Self> {
NonZeroU32::new(x ^ u32::MAX).map(Self)
}
#[inline]
unsafe fn new_unchecked(i: u32) -> Self {
Key(unsafe { NonZeroU32::new_unchecked(i ^ u32::MAX) })
}
#[inline]
fn from_index(i: usize) -> Self {
if usize::BITS >= 32 {
assert!(i < u32::MAX as usize);
}
unsafe { Self::new_unchecked(i as u32) }
}
}
pub struct ParaCord<S = foldhash::fast::RandomState> {
inner: slice::ParaCord<u8, S>,
}
impl<S> fmt::Debug for ParaCord<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_map().entries(self.iter()).finish()
}
}
impl Default for ParaCord {
#[inline]
fn default() -> Self {
Self::with_hasher(foldhash::fast::RandomState::default())
}
}
impl<S: BuildHasher> ParaCord<S> {
#[inline]
pub fn with_hasher(hasher: S) -> Self {
Self {
inner: slice::ParaCord::with_hasher(hasher),
}
}
#[inline]
pub fn get(&self, s: &str) -> Option<Key> {
self.inner.get(s.as_bytes())
}
#[inline]
pub fn get_or_intern(&self, s: &str) -> Key {
self.inner.get_or_intern(s.as_bytes())
}
}
impl<S> ParaCord<S> {
#[inline]
pub fn try_resolve(&self, key: Key) -> Option<&str> {
self.inner
.try_resolve(key)
.map(|s| unsafe { core::str::from_utf8_unchecked(s) })
}
#[inline]
pub fn resolve(&self, key: Key) -> &str {
let b = self.inner.resolve(key);
unsafe { core::str::from_utf8_unchecked(b) }
}
#[inline]
pub unsafe fn resolve_unchecked(&self, key: Key) -> &str {
let b = unsafe { self.inner.resolve_unchecked(key) };
unsafe { core::str::from_utf8_unchecked(b) }
}
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (Key, &str)> {
self.into_iter()
}
#[inline]
pub fn clear(&mut self) {
self.inner.clear();
}
#[cfg(test)]
pub(crate) fn current_memory_usage(&mut self) -> usize {
self.inner.current_memory_usage()
}
}
impl<S> Index<Key> for ParaCord<S> {
type Output = str;
fn index(&self, index: Key) -> &Self::Output {
self.resolve(index)
}
}
#[repr(transparent)]
struct AsBytes<S: AsRef<str>>(S);
impl<S: AsRef<str>> AsRef<[u8]> for AsBytes<S> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref().as_bytes()
}
}
impl<I: AsRef<str>, S: BuildHasher + Default> FromIterator<I> for ParaCord<S> {
fn from_iter<A: IntoIterator<Item = I>>(iter: A) -> Self {
Self {
inner: iter.into_iter().map(AsBytes).collect(),
}
}
}
impl<I: AsRef<str>, S: BuildHasher> Extend<I> for ParaCord<S> {
fn extend<A: IntoIterator<Item = I>>(&mut self, iter: A) {
self.inner.extend(iter.into_iter().map(AsBytes));
}
}
mod iter_private {
use crate::Key;
pub struct Iter<'a> {
pub(crate) inner: crate::slice::iter_private::Iter<'a, u8>,
}
impl<'a> Iterator for Iter<'a> {
type Item = (Key, &'a str);
fn next(&mut self) -> Option<Self::Item> {
let (key, s) = self.inner.next()?;
Some(unsafe { (key, core::str::from_utf8_unchecked(s)) })
}
}
}
impl<'a, S> IntoIterator for &'a ParaCord<S> {
type Item = (Key, &'a str);
type IntoIter = iter_private::Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
iter_private::Iter {
inner: self.inner.into_iter(),
}
}
}
#[cfg(test)]
mod tests {
use std::collections::hash_map::RandomState;
use std::sync::{Arc, Barrier};
use std::thread;
use crate::{Key, ParaCord};
#[test]
fn works() {
let paracord = ParaCord::default();
let foo = paracord.get_or_intern("foo");
let bar = paracord.get_or_intern("bar");
let foo2 = paracord.get_or_intern("foo");
assert_eq!(foo, foo2);
assert_ne!(foo, bar);
assert_eq!(paracord.resolve(foo), "foo");
assert_eq!(paracord.resolve(bar), "bar");
}
#[test]
fn with_hasher() {
let paracord: ParaCord<RandomState> = ParaCord::with_hasher(RandomState::new());
let key = paracord.get_or_intern("Test");
assert_eq!("Test", paracord.resolve(key));
}
#[test]
fn get_or_intern() {
let paracord = ParaCord::default();
let a = paracord.get_or_intern("A");
assert_eq!(a, paracord.get_or_intern("A"));
let b = paracord.get_or_intern("B");
assert_eq!(b, paracord.get_or_intern("B"));
let c = paracord.get_or_intern("C");
assert_eq!(c, paracord.get_or_intern("C"));
}
#[test]
#[cfg(not(miri))]
fn get_or_intern_threaded() {
const THREADS: usize = 10;
let barrier = Barrier::new(THREADS);
let paracord = ParaCord::default();
std::thread::scope(|s| {
for _ in 0..THREADS {
s.spawn(|| {
barrier.wait();
let a = paracord.get_or_intern("A");
assert_eq!(a, paracord.get_or_intern("A"));
let b = paracord.get_or_intern("B");
assert_eq!(b, paracord.get_or_intern("B"));
let c = paracord.get_or_intern("C");
assert_eq!(c, paracord.get_or_intern("C"));
});
}
});
}
#[test]
fn get() {
let paracord = ParaCord::default();
let key = paracord.get_or_intern("A");
assert_eq!(Some(key), paracord.get("A"));
}
#[test]
fn get_threaded() {
let paracord = Arc::new(ParaCord::default());
let key = paracord.get_or_intern("A");
let moved = Arc::clone(¶cord);
thread::spawn(move || {
assert_eq!(Some(key), moved.get("A"));
});
assert_eq!(Some(key), paracord.get("A"));
}
#[test]
fn resolve() {
let paracord = ParaCord::default();
let key = paracord.get_or_intern("A");
assert_eq!("A", paracord.resolve(key));
}
#[test]
#[should_panic]
fn resolve_panics() {
let paracord = ParaCord::default();
paracord.resolve(Key::try_from_repr(100).unwrap());
}
#[test]
fn resolve_threaded() {
let paracord = Arc::new(ParaCord::default());
let key = paracord.get_or_intern("A");
let moved = Arc::clone(¶cord);
thread::spawn(move || {
assert_eq!("A", moved.resolve(key));
});
assert_eq!("A", paracord.resolve(key));
}
#[test]
fn resolve_panics_threaded() {
let paracord = Arc::new(ParaCord::default());
let key = paracord.get_or_intern("A");
let moved = Arc::clone(¶cord);
let handle = thread::spawn(move || {
assert_eq!("A", moved.resolve(key));
moved.resolve(Key::try_from_repr(100).unwrap());
});
assert_eq!("A", paracord.resolve(key));
assert!(handle.join().is_err());
}
#[test]
fn try_resolve() {
let paracord = ParaCord::default();
let key = paracord.get_or_intern("A");
assert_eq!(Some("A"), paracord.try_resolve(key));
assert_eq!(None, paracord.try_resolve(Key::try_from_repr(100).unwrap()));
}
#[test]
fn try_resolve_threaded() {
let paracord = Arc::new(ParaCord::default());
let key = paracord.get_or_intern("A");
let moved = Arc::clone(¶cord);
thread::spawn(move || {
assert_eq!(Some("A"), moved.try_resolve(key));
assert_eq!(None, moved.try_resolve(Key::try_from_repr(100).unwrap()));
});
assert_eq!(Some("A"), paracord.try_resolve(key));
assert_eq!(None, paracord.try_resolve(Key::try_from_repr(100).unwrap()));
}
#[test]
fn len() {
let paracord = ParaCord::default();
paracord.get_or_intern("A");
paracord.get_or_intern("B");
paracord.get_or_intern("C");
assert_eq!(paracord.len(), 3);
}
#[test]
fn empty() {
let paracord = ParaCord::default();
assert!(paracord.is_empty());
}
#[test]
fn drops() {
let _ = ParaCord::default();
}
#[test]
fn drop_threaded() {
let paracord = Arc::new(ParaCord::default());
let moved = Arc::clone(¶cord);
thread::spawn(move || {
let _ = moved;
});
}
#[test]
fn memory() {
let mut paracord = ParaCord::default();
paracord.get_or_intern("A");
paracord.get_or_intern("B");
paracord.get_or_intern("C");
assert!(paracord.current_memory_usage() > 0);
}
#[test]
fn clear() {
let mut paracord = ParaCord::default();
let k1 = paracord.get_or_intern("A");
paracord.clear();
assert!(paracord.try_resolve(k1).is_none());
assert!(paracord.is_empty());
}
#[test]
fn iter() {
let paracord = ParaCord::default();
paracord.get_or_intern("A");
paracord.get_or_intern("B");
paracord.get_or_intern("C");
let values: Vec<_> = paracord.iter().map(|(k, v)| (k.into_repr(), v)).collect();
assert_eq!(values.len(), 3);
assert!(values.contains(&(0, "A")));
assert!(values.contains(&(1, "B")));
assert!(values.contains(&(2, "C")));
}
#[test]
fn from_iter() {
let paracord: ParaCord = ["a", "b", "c", "d", "e"].iter().collect();
assert!(paracord.get("a").is_some());
assert!(paracord.get("b").is_some());
assert!(paracord.get("c").is_some());
assert!(paracord.get("d").is_some());
assert!(paracord.get("e").is_some());
}
#[test]
fn index() {
let paracord = ParaCord::default();
let key = paracord.get_or_intern("A");
assert_eq!("A", ¶cord[key]);
}
#[test]
fn extend() {
let mut paracord = ParaCord::default();
assert!(paracord.is_empty());
paracord.extend(["a", "b", "c", "d", "e"].iter());
assert!(paracord.get("a").is_some());
assert!(paracord.get("b").is_some());
assert!(paracord.get("c").is_some());
assert!(paracord.get("d").is_some());
assert!(paracord.get("e").is_some());
}
#[test]
fn get_or_intern_threaded_racy() {
const THREADS: usize = 10;
let barrier = Barrier::new(THREADS);
let paracord = ParaCord::default();
let expected = Key::try_from_repr(0).unwrap();
std::thread::scope(|s| {
for _ in 0..THREADS {
s.spawn(|| {
barrier.wait();
assert_eq!(expected, paracord.get_or_intern("A"));
assert_eq!(expected, paracord.get_or_intern("A"));
assert_eq!(expected, paracord.get_or_intern("A"));
assert_eq!(expected, paracord.get_or_intern("A"));
});
}
});
}
#[test]
#[cfg(feature = "serde")]
fn serde() {
let key = crate::DefaultKey::new("hello");
serde_test::assert_de_tokens(&key, &[serde_test::Token::Str("hello")]);
serde_test::assert_ser_tokens(&key, &[serde_test::Token::Str("hello")]);
}
#[test]
#[cfg(not(miri))]
fn memory_usage() {
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use rand_distr::Zipf;
let endpoint_dist = Zipf::new(500000.0, 0.8).unwrap();
let endpoints = StdRng::seed_from_u64(272488357).sample_iter(endpoint_dist);
let mut interner = ParaCord::default();
const N: usize = 1_000_000;
let mut verify = Vec::with_capacity(N);
for endpoint in endpoints.take(N) {
let endpoint = format!("ep-string-interning-{endpoint}");
let key = interner.get_or_intern(&endpoint);
verify.push((endpoint, key));
}
for (s, key) in verify {
assert_eq!(interner[key], s);
}
let mem = interner.current_memory_usage();
let len = interner.len();
assert_eq!(mem / len, 57);
}
}