diff --git a/examples/non_string_keys.rs b/examples/non_string_keys.rs new file mode 100644 index 0000000..8891fd3 --- /dev/null +++ b/examples/non_string_keys.rs @@ -0,0 +1,24 @@ +use lasso::Rodeo; + +fn main() { + // Create a rodeo that interns Vec instead of String + let mut rodeo: Rodeo> = Rodeo::new(); + + // Intern some integer sequences + let a = rodeo.get_or_intern(vec![1, 2, 3]); + let b = rodeo.get_or_intern(vec![4, 5, 6, 7, 8]); + + // Interning the same value returns the same key + let a2 = rodeo.get_or_intern(vec![1, 2, 3]); + assert_eq!(a, a2); + + // Resolve keys back to values + assert_eq!(rodeo.resolve(&a), &[1, 2, 3]); + assert_eq!(rodeo.resolve(&b), &[4, 5, 6, 7, 8]); + + // Lookup by value + assert_eq!(rodeo.get([1, 2, 3].as_slice()), Some(a)); + assert_eq!(rodeo.get([7, 8, 9].as_slice()), None); + + println!("Interned {} sequences", rodeo.len()); +} diff --git a/src/arenas/atomic_bucket.rs b/src/arenas/atomic_bucket.rs index e46ac61..4493c9a 100644 --- a/src/arenas/atomic_bucket.rs +++ b/src/arenas/atomic_bucket.rs @@ -1,31 +1,34 @@ -use crate::{LassoError, LassoErrorKind, LassoResult}; +use crate::{ + rodeo::{Internable, InternableRef}, + LassoError, LassoErrorKind, LassoResult, +}; use alloc::alloc::{alloc, dealloc, Layout}; use core::{ hint, - mem::{align_of, size_of}, + marker::PhantomData, + mem::size_of, num::NonZeroUsize, ptr::{self, addr_of_mut, NonNull}, - slice, sync::atomic::{AtomicPtr, AtomicUsize, Ordering}, }; -pub(super) struct AtomicBucketList { +pub(super) struct AtomicBucketList { /// The first bucket in the list, will be null if the list currently /// has no buckets - head: AtomicPtr, + head: AtomicPtr>, } -impl AtomicBucketList { +impl AtomicBucketList { /// Create a new bucket list pub fn new(first_bucket_capacity: NonZeroUsize) -> LassoResult { - let bucket = AtomicBucket::with_capacity(first_bucket_capacity)?; + let bucket = AtomicBucket::::with_capacity(first_bucket_capacity)?; Ok(Self { head: AtomicPtr::new(bucket.as_ptr()), }) } - pub fn iter(&self) -> AtomicBucketIter<'_> { + pub fn iter(&self) -> AtomicBucketIter<'_, T> { AtomicBucketIter { current: &self.head, } @@ -42,7 +45,7 @@ impl AtomicBucketList { self.len() == 0 } - pub fn push_front(&self, bucket: BucketRef) { + pub fn push_front(&self, bucket: BucketRef) { let bucket_ptr = bucket.as_ptr(); let mut head_ptr = self.head.load(Ordering::Acquire); @@ -73,7 +76,7 @@ impl AtomicBucketList { } } -impl Drop for AtomicBucketList { +impl Drop for AtomicBucketList { fn drop(&mut self) { // Safety: We should have exclusive access to all buckets unsafe { @@ -89,7 +92,7 @@ impl Drop for AtomicBucketList { // Get the layout of the current bucket so we can deallocate it let capacity = (*current_ptr).capacity; - let layout = AtomicBucket::layout(capacity) + let layout = AtomicBucket::::layout(capacity) .expect("buckets with invalid capacities can't be constructed"); // Deallocate all memory that the bucket allocated @@ -99,12 +102,12 @@ impl Drop for AtomicBucketList { } } -pub(super) struct AtomicBucketIter<'a> { - current: &'a AtomicPtr, +pub(super) struct AtomicBucketIter<'a, T: Internable> { + current: &'a AtomicPtr>, } -impl<'a> Iterator for AtomicBucketIter<'a> { - type Item = BucketRef; +impl<'a, T: Internable> Iterator for AtomicBucketIter<'a, T> { + type Item = BucketRef; fn next(&mut self) -> Option { let current = self.current.load(Ordering::Acquire); @@ -121,11 +124,11 @@ impl<'a> Iterator for AtomicBucketIter<'a> { /// A unique reference to an atomic bucket #[repr(transparent)] -pub(super) struct UniqueBucketRef { - bucket: BucketRef, +pub(super) struct UniqueBucketRef { + bucket: BucketRef, } -impl UniqueBucketRef { +impl UniqueBucketRef { /// Create a new unique bucket ref /// /// # Safety @@ -133,14 +136,14 @@ impl UniqueBucketRef { /// The pointer must have exclusive, mutable and unique access to the pointed-to /// bucket #[inline] - const unsafe fn new(bucket: NonNull) -> Self { + const unsafe fn new(bucket: NonNull>) -> Self { Self { bucket: unsafe { BucketRef::new(bucket) }, } } #[inline] - pub const fn as_ptr(&self) -> *mut AtomicBucket { + pub const fn as_ptr(&self) -> *mut AtomicBucket { self.bucket.as_ptr() } @@ -180,64 +183,72 @@ impl UniqueBucketRef { /// /// # Safety /// - /// The returned `&'static str` (and all copies of it) must be dropped + /// The returned `&'static T::Ref` (and all copies of it) must be dropped /// before the current bucket is, as this bucket contains the backing - /// memory for the string. + /// memory for the data. /// Additionally, the underlying [`AtomicBucket`] must have enough room - /// to store the entire slice and the given slice must be valid utf-8 data. + /// to store the entire value (including alignment padding). /// - pub unsafe fn push_slice(&mut self, slice: &[u8]) -> &'static str { + pub unsafe fn push_slice(&mut self, value: &T::Ref) -> &'static T::Ref { let len = self.len(); + let slice = value.as_bytes(); + let count = value.len(); + + // Align the index to the required alignment for T::Ref + let align = T::Ref::ALIGNMENT; + let aligned_len = (len + align - 1) & !(align - 1); if cfg!(debug_assertions) { let capacity = self.capacity().get(); - debug_assert_ne!(len, capacity); - debug_assert!(slice.len() <= capacity - len); + debug_assert_ne!(aligned_len, capacity); + debug_assert!(aligned_len + slice.len() <= capacity); } - // Get a pointer to the start of the free data - let ptr = unsafe { addr_of_mut!((*self.as_ptr())._data).cast::().add(len) }; + // Get a pointer to the aligned start of the free data + let ptr = unsafe { + addr_of_mut!((*self.as_ptr())._data) + .cast::() + .add(aligned_len) + }; - // Make the slice that we'll fill with the string's data - let target = unsafe { slice::from_raw_parts_mut(ptr, slice.len()) }; - // Copy the data from the source string into the bucket's buffer - target.copy_from_slice(slice); + // Copy the data from the source into the bucket's buffer + unsafe { ptr.copy_from_nonoverlapping(slice.as_ptr(), slice.len()) }; - // Increment the index so that the string we just added isn't overwritten + // Increment the index so that the data we just added isn't overwritten // Safety: All bytes are initialized and the length is <= capacity - unsafe { self.set_len(len + slice.len()) }; - - // Create a string from that slice - // Safety: The source string was valid utf8, so the created buffer will be as well + unsafe { self.set_len(aligned_len + slice.len()) }; - unsafe { core::str::from_utf8_unchecked(target) } + // Create a reference from the allocated data + // Safety: The source data was valid, so the created buffer will be as well. + // The pointer is properly aligned because we aligned the index above. + unsafe { T::Ref::from_raw_parts(ptr, count) } } #[inline] - pub(crate) const fn into_ref(self) -> BucketRef { + pub(crate) fn into_ref(self) -> BucketRef { self.bucket } } /// A reference to an [`AtomicBucket`] #[repr(transparent)] -pub(super) struct BucketRef { - bucket: NonNull, +pub(super) struct BucketRef { + bucket: NonNull>, } -impl BucketRef { +impl BucketRef { /// Create a new [`BucketRef`] /// /// # Safety /// /// `bucket` must be a valid pointer to an [`AtomicBucket`] - const unsafe fn new(bucket: NonNull) -> Self { + const unsafe fn new(bucket: NonNull>) -> Self { Self { bucket } } #[inline] - pub const fn as_ptr(&self) -> *mut AtomicBucket { + pub const fn as_ptr(&self) -> *mut AtomicBucket { self.bucket.as_ptr() } @@ -261,16 +272,22 @@ impl BucketRef { unsafe { addr_of_mut!((*self.as_ptr())._data).cast::().add(start) } } + /// Try to atomically reserve space for `additional` bytes with the given alignment. + /// Returns the aligned start position on success. pub fn try_inc_length(&self, additional: usize) -> Result { debug_assert_ne!(additional, 0); let length = self.length(); let capacity = self.capacity().get(); + let align = T::Ref::ALIGNMENT; // TODO: Add backoff to this loop so we don't thrash it let mut len = length.load(Ordering::Acquire); for _ in 0..100 { - let new_length = len + additional; + // Compute the aligned start position + let aligned_start = (len + align - 1) & !(align - 1); + let new_length = aligned_start + additional; + if new_length <= capacity { match length.compare_exchange_weak( len, @@ -279,8 +296,8 @@ impl BucketRef { Ordering::Acquire, ) { Ok(_) => { - debug_assert!(len < capacity && len + additional <= capacity); - return Ok(len); + debug_assert!(aligned_start < capacity && new_length <= capacity); + return Ok(aligned_start); } Err(loaded) => { hint::spin_loop(); @@ -297,7 +314,7 @@ impl BucketRef { } #[repr(C)] -pub(super) struct AtomicBucket { +pub(super) struct AtomicBucket { /// The next bucket in the list, will be null if this is the last bucket next: AtomicPtr, @@ -314,11 +331,16 @@ pub(super) struct AtomicBucket { /// Invariant: Never touch this field manually, it contains uninitialized data up /// to the length of `capacity` _data: [u8; 0], + + /// Marker for the internable type + _marker: PhantomData, } -impl AtomicBucket { +impl AtomicBucket { + const ALIGN: usize = T::Ref::ALIGNMENT; + /// Allocates a bucket with space for `capacity` items - pub(crate) fn with_capacity(capacity: NonZeroUsize) -> LassoResult { + pub(crate) fn with_capacity(capacity: NonZeroUsize) -> LassoResult> { // Create the bucket's layout let layout = Self::layout(capacity)?; debug_assert_ne!(layout.size(), 0); @@ -360,14 +382,11 @@ impl AtomicBucket { let len = Layout::new::(); let cap = Layout::new::(); - // Safety: Align will always be a non-zero power of two and the + // Safety: ALIGN is a non-zero power of two (checked at construction) and the // size will not overflow when rounded up - debug_assert!( - Layout::from_size_align(size_of::() * capacity.get(), align_of::()).is_ok() - ); - let data = unsafe { - Layout::from_size_align_unchecked(size_of::() * capacity.get(), align_of::()) - }; + debug_assert!(Layout::from_size_align(size_of::() * capacity.get(), Self::ALIGN).is_ok()); + let data = + unsafe { Layout::from_size_align_unchecked(size_of::() * capacity.get(), Self::ALIGN) }; next.extend(len) .and_then(|(layout, _)| layout.extend(cap)) @@ -377,5 +396,5 @@ impl AtomicBucket { } } -unsafe impl Send for AtomicBucket {} -unsafe impl Sync for AtomicBucket {} +unsafe impl Send for AtomicBucket {} +unsafe impl Sync for AtomicBucket {} diff --git a/src/arenas/bucket.rs b/src/arenas/bucket.rs index ab00fe4..83f4f50 100644 --- a/src/arenas/bucket.rs +++ b/src/arenas/bucket.rs @@ -1,38 +1,35 @@ -use crate::{LassoError, LassoErrorKind, LassoResult}; +use crate::{rodeo::Internable, rodeo::InternableRef, LassoError, LassoErrorKind, LassoResult}; use alloc::alloc::{alloc, dealloc, Layout}; -use core::{ - mem::{align_of, size_of}, - num::NonZeroUsize, - ptr::NonNull, - slice, -}; +use core::{marker::PhantomData, mem::size_of, num::NonZeroUsize, ptr::NonNull}; /// A bucket to hold a number of stored items -pub(super) struct Bucket { +pub(super) struct Bucket { /// The start of uninitialized memory within `items` index: usize, /// A pointer to the start of the data items: NonNull, - /// The total number of Ts that can be stored + /// The total number of bytes that can be stored capacity: NonZeroUsize, + /// Marker for the internable type + _marker: PhantomData, } -impl Bucket { - /// Allocates a bucket with space for `capacity` items +impl Bucket { + const ALIGN: usize = T::Ref::ALIGNMENT; + + /// Allocates a bucket with space for `capacity` bytes pub(crate) fn with_capacity(capacity: NonZeroUsize) -> LassoResult { + const { assert!(T::Ref::ALIGNMENT.is_power_of_two(), "alignment must be a power of two") }; + unsafe { - debug_assert!(Layout::from_size_align( - size_of::() * capacity.get(), - align_of::(), - ) - .is_ok()); + debug_assert!( + Layout::from_size_align(size_of::() * capacity.get(), Self::ALIGN).is_ok() + ); - // Safety: Align will always be a non-zero power of two and the + // Safety: ALIGN is a non-zero power of two and the // size will not overflow when rounded up - let layout = Layout::from_size_align_unchecked( - size_of::() * capacity.get(), - align_of::(), - ); + let layout = + Layout::from_size_align_unchecked(size_of::() * capacity.get(), Self::ALIGN); // Allocate the bucket's memory let items = NonNull::new(alloc(layout)) @@ -44,6 +41,7 @@ impl Bucket { index: 0, capacity, items, + _marker: PhantomData, }) } } @@ -64,37 +62,50 @@ impl Bucket { self.index = 0; } - /// Push a slice to the current bucket, returning a pointer to it + /// Push a value to the current bucket, returning a pointer to it /// /// # Safety /// - /// The current bucket must have room for all bytes of the slice and - /// the caller promises to forget the reference before the arena is dropped. - /// Additionally, `slice` must be valid UTF-8 and should come from an `&str` + /// The current bucket must have room for all bytes of the value (including alignment padding) + /// and the caller promises to forget the reference before the arena is dropped. /// - pub(crate) unsafe fn push_slice(&mut self, slice: &[u8]) -> &'static str { + pub(crate) unsafe fn push_slice(&mut self, value: &T::Ref) -> &'static T::Ref { debug_assert!(!self.is_full()); - debug_assert!(slice.len() <= self.capacity.get() - self.index); + + let slice = value.as_bytes(); + let count = value.len(); + + // Align the index to the required alignment for T::Ref + let aligned_index = (self.index + Self::ALIGN - 1) & !(Self::ALIGN - 1); + + debug_assert!(aligned_index + slice.len() <= self.capacity.get()); unsafe { - // Get a pointer to the start of free bytes - let ptr = self.items.as_ptr().add(self.index); - - // Make the slice that we'll fill with the string's data - let target = slice::from_raw_parts_mut(ptr, slice.len()); - // Copy the data from the source string into the bucket's buffer - target.copy_from_slice(slice); - // Increment the index so that the string we just made isn't overwritten - self.index += slice.len(); - - // Create a string from that slice - // Safety: The source string was valid utf8, so the created buffer will be as well - core::str::from_utf8_unchecked(target) + // Get a pointer to the aligned start of free bytes + let ptr = self.items.as_ptr().add(aligned_index); + + // Copy the data from the source into the bucket's buffer + ptr.copy_from_nonoverlapping(slice.as_ptr(), slice.len()); + // Increment the index so that the data we just added isn't overwritten + self.index = aligned_index + slice.len(); + + // Create a reference from the allocated data + // Safety: The source data was valid, so the created buffer will be as well. + // The pointer is properly aligned because we aligned the index above. + T::Ref::from_raw_parts(ptr, count) } } + + /// Returns the space needed to store `len` bytes with the given alignment, + /// accounting for any padding needed. + pub(crate) fn space_needed(&self, len: usize) -> usize { + let aligned_index = (self.index + Self::ALIGN - 1) & !(Self::ALIGN - 1); + let padding = aligned_index - self.index; + padding + len + } } -impl Drop for Bucket { +impl Drop for Bucket { fn drop(&mut self) { // Safety: We have exclusive access to the pointers since the contract of // `store_str` should be withheld @@ -103,23 +114,20 @@ impl Drop for Bucket { debug_assert!(Layout::from_size_align( size_of::() * self.capacity.get(), - align_of::(), + Self::ALIGN, ) .is_ok()); // Deallocate all memory that the bucket allocated dealloc( items, - // Safety: Align will always be a non-zero power of two and the - // size will not overflow when rounded up - Layout::from_size_align_unchecked( - size_of::() * self.capacity.get(), - align_of::(), - ), + // Safety: ALIGN is a non-zero power of two (checked at construction) + // and the size will not overflow when rounded up + Layout::from_size_align_unchecked(size_of::() * self.capacity.get(), Self::ALIGN), ); } } } -unsafe impl Send for Bucket {} -unsafe impl Sync for Bucket {} +unsafe impl Send for Bucket {} +unsafe impl Sync for Bucket {} diff --git a/src/arenas/lockfree.rs b/src/arenas/lockfree.rs index 010b319..95671e8 100644 --- a/src/arenas/lockfree.rs +++ b/src/arenas/lockfree.rs @@ -1,30 +1,32 @@ use crate::{ arenas::atomic_bucket::{AtomicBucket, AtomicBucketList}, + rodeo::{Internable, InternableRef}, Capacity, LassoError, LassoErrorKind, LassoResult, MemoryLimits, }; use core::{ fmt::{self, Debug}, + marker::PhantomData, num::NonZeroUsize, - slice, str, sync::atomic::{AtomicUsize, Ordering}, }; /// An arena allocator that dynamically grows in size when needed, allocating memory in large chunks -pub(crate) struct LockfreeArena { +pub(crate) struct LockfreeArena { /// All the internal buckets, storing all allocated and unallocated items // TODO: We could keep around a second list of buckets to store filled buckets // in to keep us from having to iterate over them, need more tests to // see what the impact of that is - buckets: AtomicBucketList, + buckets: AtomicBucketList, /// The default capacity of each bucket /// /// Invariant: `bucket_capacity` must never be zero bucket_capacity: AtomicUsize, memory_usage: AtomicUsize, max_memory_usage: AtomicUsize, + _marker: PhantomData, } -impl LockfreeArena { +impl LockfreeArena { /// Create a new Arena with the default bucket size of 4096 bytes pub fn new(capacity: NonZeroUsize, max_memory_usage: usize) -> LassoResult { Ok(Self { @@ -34,6 +36,7 @@ impl LockfreeArena { // The current capacity is whatever size the bucket we just allocated is memory_usage: AtomicUsize::new(capacity.get()), max_memory_usage: AtomicUsize::new(max_memory_usage), + _marker: PhantomData, }) } @@ -80,16 +83,17 @@ impl LockfreeArena { /// /// The reference passed back must be dropped before the arena that created it is /// - pub unsafe fn store_str(&self, string: &str) -> LassoResult<&'static str> { + pub unsafe fn store_str(&self, string: &T::Ref) -> LassoResult<&'static T::Ref> { // If the string is empty, simply return an empty string. // This ensures that only strings with lengths greater // than zero will be allocated within the arena if string.is_empty() { - return Ok(""); + return Ok(T::Ref::empty()); } let slice = string.as_bytes(); - debug_assert_ne!(slice.len(), 0); + let byte_len = slice.len(); + let count = string.len(); // Iterate over all of the buckets within the list while attempting to find one // that has enough space to fit our string within it @@ -101,20 +105,18 @@ impl LockfreeArena { // retries within this loop, the worst-case performance suffers in exchange for potentially // better memory usage. for bucket in self.buckets.iter() { - if let Ok(start) = bucket.try_inc_length(slice.len()) { - // Safety: We now have exclusive access to `bucket[start..start + slice.len()]` - let allocated = unsafe { bucket.slice_mut(start) }; + if let Ok(aligned_start) = bucket.try_inc_length(byte_len) { + // Safety: We now have exclusive access to `bucket[aligned_start..aligned_start + byte_len]` + // and aligned_start is properly aligned for T::Ref + let allocated = unsafe { bucket.slice_mut(aligned_start) }; // Copy the given slice into the allocation - unsafe { allocated.copy_from_nonoverlapping(slice.as_ptr(), slice.len()) }; + unsafe { allocated.copy_from_nonoverlapping(slice.as_ptr(), byte_len) }; - // Return the successfully allocated string - let string = unsafe { - str::from_utf8_unchecked(slice::from_raw_parts(allocated, slice.len())) - }; - return Ok(string); + // Return the successfully allocated data + return Ok(unsafe { T::Ref::from_raw_parts(allocated, count) }); } - // Otherwise the bucket doesn't have sufficient capacity for the string + // Otherwise the bucket doesn't have sufficient capacity for the data // so we carry on searching through allocated buckets } @@ -126,20 +128,20 @@ impl LockfreeArena { // If the current string's length is greater than the doubled current capacity, allocate a bucket exactly the // size of the large string and push it back in the buckets vector. This ensures that obscenely large strings will // not permanently affect the resource consumption of the interner - if slice.len() > next_capacity { + if byte_len > next_capacity { // Check that we haven't exhausted our memory limit - self.allocate_memory(slice.len())?; + self.allocate_memory(byte_len)?; - // Safety: `len` will never be zero since we explicitly handled zero-length strings + // Safety: `byte_len` will never be zero since we explicitly handled zero-length strings // at the beginning of the function - let non_zero_len = unsafe { NonZeroUsize::new_unchecked(slice.len()) }; - debug_assert_ne!(slice.len(), 0); + let non_zero_len = unsafe { NonZeroUsize::new_unchecked(byte_len) }; + debug_assert_ne!(byte_len, 0); - let mut bucket = AtomicBucket::with_capacity(non_zero_len)?; + let mut bucket = AtomicBucket::::with_capacity(non_zero_len)?; // Safety: The new bucket will have exactly enough room for the string and we have // exclusive access to the bucket since we just created it - let allocated_string = unsafe { bucket.push_slice(slice) }; + let allocated_string = unsafe { bucket.push_slice(string) }; self.buckets.push_front(bucket.into_ref()); Ok(allocated_string) @@ -155,14 +157,14 @@ impl LockfreeArena { self.allocate_memory(remaining_memory)?; // Set the capacity to twice of what it currently is to allow for fewer allocations as more strings are interned - let mut bucket = AtomicBucket::with_capacity( + let mut bucket = AtomicBucket::::with_capacity( NonZeroUsize::new(remaining_memory) .ok_or_else(|| LassoError::new(LassoErrorKind::MemoryLimitReached))?, )?; // Safety: The new bucket will have exactly enough room for the string and we have // exclusive access to the bucket since we just created it - let allocated_string = unsafe { bucket.push_slice(slice) }; + let allocated_string = unsafe { bucket.push_slice(string) }; // TODO: Push the bucket to the back or something so that we can get it somewhat out // of the search path, reduce the `n` in the `O(n)` list traversal self.buckets.push_front(bucket.into_ref()); @@ -181,10 +183,10 @@ impl LockfreeArena { let capacity = unsafe { NonZeroUsize::new_unchecked(next_capacity) }; debug_assert_ne!(next_capacity, 0); - let mut bucket = AtomicBucket::with_capacity(capacity)?; + let mut bucket = AtomicBucket::::with_capacity(capacity)?; // Safety: The new bucket will have enough room for the string - let allocated_string = unsafe { bucket.push_slice(slice) }; + let allocated_string = unsafe { bucket.push_slice(string) }; self.buckets.push_front(bucket.into_ref()); Ok(allocated_string) @@ -193,7 +195,7 @@ impl LockfreeArena { } } -impl Default for LockfreeArena { +impl Default for LockfreeArena { fn default() -> Self { Self::new( Capacity::default().bytes, @@ -203,7 +205,7 @@ impl Default for LockfreeArena { } } -impl Debug for LockfreeArena { +impl Debug for LockfreeArena { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { struct TotalBuckets(usize); @@ -235,10 +237,12 @@ impl Debug for LockfreeArena { #[cfg(test)] mod tests { use super::*; + #[cfg(feature = "no-std")] + use alloc::string::String; #[test] fn string() { - let arena = LockfreeArena::default(); + let arena = LockfreeArena::::default(); unsafe { let idx = arena.store_str("test"); @@ -249,7 +253,7 @@ mod tests { #[test] fn empty_str() { - let arena = LockfreeArena::default(); + let arena = LockfreeArena::::default(); unsafe { let zst = arena.store_str(""); @@ -264,7 +268,7 @@ mod tests { #[test] fn exponential_allocations() { - let arena = LockfreeArena::default(); + let arena = LockfreeArena::::default(); let mut len = 4096; for _ in 0..10 { @@ -278,7 +282,7 @@ mod tests { #[test] fn memory_exhausted() { - let arena = LockfreeArena::new(NonZeroUsize::new(10).unwrap(), 10).unwrap(); + let arena = LockfreeArena::::new(NonZeroUsize::new(10).unwrap(), 10).unwrap(); unsafe { assert!(arena.store_str("0123456789").is_ok()); @@ -296,20 +300,24 @@ mod tests { #[test] fn allocate_too_much() { - let arena = LockfreeArena::new(NonZeroUsize::new(1).unwrap(), 10).unwrap(); + let arena = LockfreeArena::::new(NonZeroUsize::new(1).unwrap(), 10).unwrap(); unsafe { - let err = arena.store_str("abcdefghijklmnopqrstuvwxyz").unwrap_err(); + let err = arena + .store_str("abcdefghijklmnopqrstuvwxyz") + .unwrap_err(); assert!(err.kind().is_memory_limit()); } } #[test] fn allocate_more_than_double() { - let arena = LockfreeArena::new(NonZeroUsize::new(1).unwrap(), 1000).unwrap(); + let arena = LockfreeArena::::new(NonZeroUsize::new(1).unwrap(), 1000).unwrap(); unsafe { - assert!(arena.store_str("abcdefghijklmnopqrstuvwxyz").is_ok()); + assert!(arena + .store_str("abcdefghijklmnopqrstuvwxyz") + .is_ok()); } } } diff --git a/src/arenas/mod.rs b/src/arenas/mod.rs index 4e2b6d1..ce9cbb5 100644 --- a/src/arenas/mod.rs +++ b/src/arenas/mod.rs @@ -10,19 +10,20 @@ mod lockfree; pub(crate) use lockfree::LockfreeArena; pub(crate) use single_threaded::Arena; +use crate::rodeo::Internable; use core::fmt::{self, Debug}; /// A wrapper type to abstract over all arena types /// /// Used for readers & resolvers to allow them to be created from /// any arena type without using dynamic dispatch or allocation -pub(crate) enum AnyArena { - Arena(Arena), +pub(crate) enum AnyArena { + Arena(Arena), #[cfg(feature = "multi-threaded")] - Lockfree(LockfreeArena), + Lockfree(LockfreeArena), } -impl Debug for AnyArena { +impl Debug for AnyArena { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Arena(arena) => arena.fmt(f), diff --git a/src/arenas/single_threaded.rs b/src/arenas/single_threaded.rs index 2525721..b924f4f 100644 --- a/src/arenas/single_threaded.rs +++ b/src/arenas/single_threaded.rs @@ -1,20 +1,23 @@ use crate::{ - arenas::bucket::Bucket, Capacity, LassoError, LassoErrorKind, LassoResult, MemoryLimits, + arenas::bucket::Bucket, + rodeo::{Internable, InternableRef as _}, + Capacity, LassoError, LassoErrorKind, LassoResult, MemoryLimits, }; use alloc::{format, vec, vec::Vec}; -use core::{fmt, num::NonZeroUsize}; +use core::{fmt, marker::PhantomData, num::NonZeroUsize}; /// An arena allocator that dynamically grows in size when needed, allocating memory in large chunks -pub(crate) struct Arena { +pub(crate) struct Arena { /// All the internal buckets, storing all allocated and unallocated items - buckets: Vec, + buckets: Vec>, /// The default capacity of each bucket bucket_capacity: NonZeroUsize, memory_usage: usize, pub(crate) max_memory_usage: usize, + _marker: PhantomData, } -impl Arena { +impl Arena { /// Create a new Arena with the default bucket size of 4096 bytes pub fn new(capacity: NonZeroUsize, max_memory_usage: usize) -> LassoResult { Ok(Self { @@ -24,6 +27,7 @@ impl Arena { // The current capacity is whatever size the bucket we just allocated is memory_usage: capacity.get(), max_memory_usage, + _marker: PhantomData, }) } @@ -59,25 +63,23 @@ impl Arena { /// /// The reference passed back must be dropped before the arena that created it is /// - pub unsafe fn store_str(&mut self, string: &str) -> LassoResult<&'static str> { + pub unsafe fn store_str(&mut self, string: &T::Ref) -> LassoResult<&'static T::Ref> { // If the string is empty, simply return an empty string. // This ensures that only strings with lengths greater // than zero will be allocated within the arena if string.is_empty() { - return Ok(""); + return Ok(T::Ref::empty()); } - let slice = string.as_bytes(); - let len = slice.len(); - debug_assert_ne!(len, 0); + let byte_len = string.as_bytes().len(); if let Some(bucket) = self .buckets .last_mut() - .filter(|bucket| bucket.free_elements() >= len) + .filter(|bucket| bucket.free_elements() >= bucket.space_needed(byte_len)) { - // Safety: The bucket found has enough room for the slice - let allocated = unsafe { bucket.push_slice(slice) }; + // Safety: The bucket found has enough room for the value (including alignment) + let allocated = unsafe { bucket.push_slice(string) }; return Ok(allocated); } @@ -89,15 +91,16 @@ impl Arena { // If the current string's length is greater than the doubled current capacity, allocate a bucket exactly the // size of the large string and push it back in the buckets vector. This ensures that obscenely large strings will // not permanently affect the resource consumption of the interner - if len > next_capacity { + if byte_len > next_capacity { // Check that we haven't exhausted our memory limit - self.allocate_memory(len)?; + self.allocate_memory(byte_len)?; - // Safety: len will always be >= 1 - let mut bucket = Bucket::with_capacity(unsafe { NonZeroUsize::new_unchecked(len) })?; + // Safety: byte_len will always be >= 1 + let mut bucket = + Bucket::with_capacity(unsafe { NonZeroUsize::new_unchecked(byte_len) })?; // Safety: The new bucket will have exactly enough room for the string - let allocated_string = unsafe { bucket.push_slice(slice) }; + let allocated_string = unsafe { bucket.push_slice(string) }; self.buckets .insert(self.buckets.len().saturating_sub(2), bucket); @@ -116,7 +119,7 @@ impl Arena { )?; // Safety: The new bucket will have enough room for the string - let allocated_string = unsafe { bucket.push_slice(slice) }; + let allocated_string = unsafe { bucket.push_slice(string) }; self.buckets.push(bucket); Ok(allocated_string) @@ -132,7 +135,7 @@ impl Arena { let mut bucket = Bucket::with_capacity(self.bucket_capacity)?; // Safety: The new bucket will have enough room for the string - let allocated_string = unsafe { bucket.push_slice(slice) }; + let allocated_string = unsafe { bucket.push_slice(string) }; self.buckets.push(bucket); Ok(allocated_string) @@ -140,7 +143,7 @@ impl Arena { } } -impl Default for Arena { +impl Default for Arena { fn default() -> Self { Self::new( Capacity::default().bytes, @@ -150,7 +153,7 @@ impl Default for Arena { } } -impl fmt::Debug for Arena { +impl fmt::Debug for Arena { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Arena") .field( @@ -171,10 +174,12 @@ impl fmt::Debug for Arena { #[cfg(test)] mod tests { use super::*; + #[cfg(feature = "no-std")] + use alloc::string::String; #[test] fn string() { - let mut arena = Arena::default(); + let mut arena = Arena::::default(); unsafe { let idx = arena.store_str("test"); @@ -185,7 +190,7 @@ mod tests { #[test] fn empty_str() { - let mut arena = Arena::default(); + let mut arena = Arena::::default(); unsafe { let zst = arena.store_str(""); @@ -200,7 +205,7 @@ mod tests { #[test] fn exponential_allocations() { - let mut arena = Arena::default(); + let mut arena = Arena::::default(); let mut len = 4096; for _ in 0..10 { @@ -214,7 +219,7 @@ mod tests { #[test] fn memory_exhausted() { - let mut arena = Arena::new(NonZeroUsize::new(10).unwrap(), 10).unwrap(); + let mut arena = Arena::::new(NonZeroUsize::new(10).unwrap(), 10).unwrap(); unsafe { assert!(arena.store_str("0123456789").is_ok()); @@ -232,20 +237,24 @@ mod tests { #[test] fn allocate_too_much() { - let mut arena = Arena::new(NonZeroUsize::new(1).unwrap(), 10).unwrap(); + let mut arena = Arena::::new(NonZeroUsize::new(1).unwrap(), 10).unwrap(); unsafe { - let err = arena.store_str("abcdefghijklmnopqrstuvwxyz").unwrap_err(); + let err = arena + .store_str("abcdefghijklmnopqrstuvwxyz") + .unwrap_err(); assert!(err.kind().is_memory_limit()); } } #[test] fn allocate_more_than_double() { - let mut arena = Arena::new(NonZeroUsize::new(1).unwrap(), 1000).unwrap(); + let mut arena = Arena::::new(NonZeroUsize::new(1).unwrap(), 1000).unwrap(); unsafe { - assert!(arena.store_str("abcdefghijklmnopqrstuvwxyz").is_ok()); + assert!(arena + .store_str("abcdefghijklmnopqrstuvwxyz") + .is_ok()); } } } diff --git a/src/interface/boxed.rs b/src/interface/boxed.rs index a2074be..2b84db4 100644 --- a/src/interface/boxed.rs +++ b/src/interface/boxed.rs @@ -1,43 +1,42 @@ use super::{Interner, IntoReader, IntoResolver, Reader, Resolver}; -use crate::{Key, LassoResult}; +use crate::{rodeo::Internable, Key, LassoResult}; #[cfg(feature = "no-std")] use alloc::boxed::Box; -impl Interner for Box +impl Interner for Box where K: Key, - I: Interner + ?Sized + 'static, + I: Interner + ?Sized + 'static, { #[cfg_attr(feature = "inline-more", inline)] - fn get_or_intern(&mut self, val: &str) -> K { + fn get_or_intern(&mut self, val: &T::Ref) -> K { (**self).get_or_intern(val) } #[cfg_attr(feature = "inline-more", inline)] - fn try_get_or_intern(&mut self, val: &str) -> LassoResult { + fn try_get_or_intern(&mut self, val: &T::Ref) -> LassoResult { (**self).try_get_or_intern(val) } #[cfg_attr(feature = "inline-more", inline)] - fn get_or_intern_static(&mut self, val: &'static str) -> K { + fn get_or_intern_static(&mut self, val: &'static T::Ref) -> K { (**self).get_or_intern_static(val) } #[cfg_attr(feature = "inline-more", inline)] - fn try_get_or_intern_static(&mut self, val: &'static str) -> LassoResult { + fn try_get_or_intern_static(&mut self, val: &'static T::Ref) -> LassoResult { self.try_get_or_intern(val) } } -impl IntoReader for Box +impl IntoReader for Box where K: Key, - I: IntoReader + ?Sized + 'static, + I: IntoReader + ?Sized + 'static, { - type Reader = >::Reader; + type Reader = >::Reader; #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_reader(self) -> Self::Reader where Self: 'static, @@ -46,7 +45,6 @@ where } #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_reader_boxed(self: Box) -> Self::Reader where Self: 'static, @@ -55,31 +53,30 @@ where } } -impl Reader for Box +impl Reader for Box where K: Key, - I: Reader + ?Sized + 'static, + I: Reader + ?Sized + 'static, { #[cfg_attr(feature = "inline-more", inline)] - fn get(&self, val: &str) -> Option { + fn get(&self, val: &T::Ref) -> Option { (**self).get(val) } #[cfg_attr(feature = "inline-more", inline)] - fn contains(&self, val: &str) -> bool { + fn contains(&self, val: &T::Ref) -> bool { (**self).contains(val) } } -impl IntoResolver for Box +impl IntoResolver for Box where K: Key, - I: IntoResolver + ?Sized + 'static, + I: IntoResolver + ?Sized + 'static, { - type Resolver = >::Resolver; + type Resolver = >::Resolver; #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_resolver(self) -> Self::Resolver where Self: 'static, @@ -88,7 +85,6 @@ where } #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_resolver_boxed(self: Box) -> Self::Resolver where Self: 'static, @@ -97,23 +93,23 @@ where } } -impl Resolver for Box +impl Resolver for Box where K: Key, - I: Resolver + ?Sized + 'static, + I: Resolver + ?Sized + 'static, { #[cfg_attr(feature = "inline-more", inline)] - fn resolve<'a>(&'a self, key: &K) -> &'a str { + fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref { (**self).resolve(key) } #[cfg_attr(feature = "inline-more", inline)] - fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str> { + fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref> { (**self).try_resolve(key) } #[cfg_attr(feature = "inline-more", inline)] - unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a str { + unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a T::Ref { unsafe { (**self).resolve_unchecked(key) } } diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 0d125ff..f0f970c 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -6,7 +6,7 @@ mod tests; mod threaded_ref; mod threaded_rodeo; -use crate::{Key, LassoResult, Spur}; +use crate::{rodeo::Internable, Key, LassoResult, Spur}; #[cfg(feature = "no-std")] use alloc::boxed::Box; @@ -16,7 +16,7 @@ use alloc::boxed::Box; /// Note that because single-threaded [`Rodeo`](crate::Rodeo)s require mutable access to use, this /// trait does so as well. For use with [`ThreadedRodeo`](crate::ThreadedRodeo), the trait is /// implemented for `&ThreadedRodeo` as well to allow access through shared references. -pub trait Interner: Reader + Resolver { +pub trait Interner: Reader + Resolver { /// Get the key for a string, interning it if it does not yet exist /// /// # Panics @@ -25,10 +25,10 @@ pub trait Interner: Reader + Resolver { /// keys, this means that you've interned more strings than it can handle. (For [`Spur`] this /// means that `u32::MAX - 1` unique strings were interned) /// - fn get_or_intern(&mut self, val: &str) -> K; + fn get_or_intern(&mut self, val: &T::Ref) -> K; /// Get the key for a string, interning it if it does not yet exist - fn try_get_or_intern(&mut self, val: &str) -> LassoResult; + fn try_get_or_intern(&mut self, val: &T::Ref) -> LassoResult; /// Get the key for a static string, interning it if it does not yet exist /// @@ -40,54 +40,56 @@ pub trait Interner: Reader + Resolver { /// keys, this means that you've interned more strings than it can handle. (For [`Spur`] this /// means that `u32::MAX - 1` unique strings were interned) /// - fn get_or_intern_static(&mut self, val: &'static str) -> K; + fn get_or_intern_static(&mut self, val: &'static T::Ref) -> K; /// Get the key for a static string, interning it if it does not yet exist /// /// This will not reallocate or copy the given string - fn try_get_or_intern_static(&mut self, val: &'static str) -> LassoResult; + fn try_get_or_intern_static(&mut self, val: &'static T::Ref) -> LassoResult; } -impl Interner for &mut T +impl Interner for &mut R where - T: Interner, + R: Interner, { #[inline] - fn get_or_intern(&mut self, val: &str) -> K { - >::get_or_intern(self, val) + fn get_or_intern(&mut self, val: &T::Ref) -> K { + >::get_or_intern(self, val) } #[inline] - fn try_get_or_intern(&mut self, val: &str) -> LassoResult { - >::try_get_or_intern(self, val) + fn try_get_or_intern(&mut self, val: &T::Ref) -> LassoResult { + >::try_get_or_intern(self, val) } #[inline] - fn get_or_intern_static(&mut self, val: &'static str) -> K { - >::get_or_intern_static(self, val) + fn get_or_intern_static(&mut self, val: &'static T::Ref) -> K { + >::get_or_intern_static(self, val) } #[inline] - fn try_get_or_intern_static(&mut self, val: &'static str) -> LassoResult { - >::try_get_or_intern_static(self, val) + fn try_get_or_intern_static(&mut self, val: &'static T::Ref) -> LassoResult { + >::try_get_or_intern_static(self, val) } } /// A generic interface over interners that can be turned into both a [`Reader`] and a [`Resolver`] /// directly. -pub trait IntoReaderAndResolver: IntoReader + IntoResolver +pub trait IntoReaderAndResolver: + IntoReader + IntoResolver where K: Key, + T: Internable, { } /// A generic interface over interners that can be turned into a [`Reader`]. -pub trait IntoReader: Interner +pub trait IntoReader: Interner where K: Key, { /// The type of [`Reader`] the interner will be converted into - type Reader: Reader; + type Reader: Reader; /// Consumes the current [`Interner`] and converts it into a [`Reader`] to allow /// contention-free access of the interner from multiple threads @@ -108,51 +110,52 @@ where /// A generic interface that allows using any underlying interner for /// both its reading and resolution capabilities, allowing both /// `str -> key` and `key -> str` lookups -pub trait Reader: Resolver { +pub trait Reader: Resolver { /// Get a key for the given string value if it exists - fn get(&self, val: &str) -> Option; + fn get(&self, val: &T::Ref) -> Option; /// Returns `true` if the current interner contains the given string - fn contains(&self, val: &str) -> bool; + fn contains(&self, val: &T::Ref) -> bool; } -impl Reader for &T +impl Reader for &R where - T: Reader, + R: Reader, { #[inline] - fn get(&self, val: &str) -> Option { - >::get(self, val) + fn get(&self, val: &T::Ref) -> Option { + >::get(self, val) } #[inline] - fn contains(&self, val: &str) -> bool { - >::contains(self, val) + fn contains(&self, val: &T::Ref) -> bool { + >::contains(self, val) } } -impl Reader for &mut T +impl Reader for &mut R where - T: Reader, + R: Reader, { #[inline] - fn get(&self, val: &str) -> Option { - >::get(self, val) + fn get(&self, val: &T::Ref) -> Option { + >::get(self, val) } #[inline] - fn contains(&self, val: &str) -> bool { - >::contains(self, val) + fn contains(&self, val: &T::Ref) -> bool { + >::contains(self, val) } } /// A generic interface over [`Reader`]s that can be turned into a [`Resolver`]. -pub trait IntoResolver: Reader +pub trait IntoResolver: Reader where K: Key, + T: Internable, { /// The type of [`Resolver`] the reader will be converted into - type Resolver: Resolver; + type Resolver: Resolver; /// Consumes the current [`Reader`] and makes it into a [`Resolver`], allowing /// contention-free access from multiple threads with the lowest possible memory consumption @@ -172,18 +175,18 @@ where /// A generic interface that allows using any underlying interner only /// for its resolution capabilities, allowing only `key -> str` lookups -pub trait Resolver { +pub trait Resolver { /// Resolves the given key into a string /// /// # Panics /// /// Panics if the key is not contained in the current [`Resolver`] /// - fn resolve<'a>(&'a self, key: &K) -> &'a str; + fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref; /// Attempts to resolve the given key into a string, returning `None` /// if it cannot be found - fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str>; + fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref>; /// Resolves a string by its key without preforming bounds checks /// @@ -191,7 +194,7 @@ pub trait Resolver { /// /// The key must be valid for the current [`Resolver`] /// - unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a str; + unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a T::Ref; /// Returns `true` if the current interner contains the given key fn contains_key(&self, key: &K) -> bool; @@ -206,62 +209,62 @@ pub trait Resolver { } } -impl Resolver for &T +impl Resolver for &R where - T: Resolver, + R: Resolver, { #[inline] - fn resolve<'a>(&'a self, key: &K) -> &'a str { - >::resolve(self, key) + fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref { + >::resolve(self, key) } #[inline] - fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str> { - >::try_resolve(self, key) + fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref> { + >::try_resolve(self, key) } #[inline] - unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a str { - unsafe { >::resolve_unchecked(self, key) } + unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a T::Ref { + unsafe { >::resolve_unchecked(self, key) } } #[inline] fn contains_key(&self, key: &K) -> bool { - >::contains_key(self, key) + >::contains_key(self, key) } #[inline] fn len(&self) -> usize { - >::len(self) + >::len(self) } } -impl Resolver for &mut T +impl Resolver for &mut R where - T: Resolver, + R: Resolver, { #[inline] - fn resolve<'a>(&'a self, key: &K) -> &'a str { - >::resolve(self, key) + fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref { + >::resolve(self, key) } #[inline] - fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str> { - >::try_resolve(self, key) + fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref> { + >::try_resolve(self, key) } #[inline] - unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a str { - unsafe { >::resolve_unchecked(self, key) } + unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a T::Ref { + unsafe { >::resolve_unchecked(self, key) } } #[inline] fn contains_key(&self, key: &K) -> bool { - >::contains_key(self, key) + >::contains_key(self, key) } #[inline] fn len(&self) -> usize { - >::len(self) + >::len(self) } } diff --git a/src/interface/rodeo.rs b/src/interface/rodeo.rs index 96278dc..41976c5 100644 --- a/src/interface/rodeo.rs +++ b/src/interface/rodeo.rs @@ -1,53 +1,52 @@ //! Implementations of [`Interner`], [`Reader`] and [`Resolver`] for [`Rodeo`] -use crate::*; +use crate::{rodeo::Internable, *}; #[cfg(feature = "no-std")] use alloc::boxed::Box; use core::hash::BuildHasher; use interface::IntoReaderAndResolver; -impl Interner for Rodeo +impl Interner for Rodeo where K: Key, S: BuildHasher, { #[cfg_attr(feature = "inline-more", inline)] - fn get_or_intern(&mut self, val: &str) -> K { + fn get_or_intern(&mut self, val: &T::Ref) -> K { self.get_or_intern(val) } #[cfg_attr(feature = "inline-more", inline)] - fn try_get_or_intern(&mut self, val: &str) -> LassoResult { + fn try_get_or_intern(&mut self, val: &T::Ref) -> LassoResult { self.try_get_or_intern(val) } #[cfg_attr(feature = "inline-more", inline)] - fn get_or_intern_static(&mut self, val: &'static str) -> K { + fn get_or_intern_static(&mut self, val: &'static T::Ref) -> K { self.get_or_intern_static(val) } #[cfg_attr(feature = "inline-more", inline)] - fn try_get_or_intern_static(&mut self, val: &'static str) -> LassoResult { + fn try_get_or_intern_static(&mut self, val: &'static T::Ref) -> LassoResult { self.try_get_or_intern_static(val) } } -impl IntoReaderAndResolver for Rodeo +impl IntoReaderAndResolver for Rodeo where K: Key, S: BuildHasher, { } -impl IntoReader for Rodeo +impl IntoReader for Rodeo where K: Key, S: BuildHasher, { - type Reader = RodeoReader; + type Reader = RodeoReader; #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_reader(self) -> Self::Reader where Self: 'static, @@ -56,7 +55,6 @@ where } #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_reader_boxed(self: Box) -> Self::Reader where Self: 'static, @@ -65,31 +63,30 @@ where } } -impl Reader for Rodeo +impl Reader for Rodeo where K: Key, S: BuildHasher, { #[cfg_attr(feature = "inline-more", inline)] - fn get(&self, val: &str) -> Option { + fn get(&self, val: &T::Ref) -> Option { self.get(val) } #[cfg_attr(feature = "inline-more", inline)] - fn contains(&self, val: &str) -> bool { + fn contains(&self, val: &T::Ref) -> bool { self.contains(val) } } -impl IntoResolver for Rodeo +impl IntoResolver for Rodeo where K: Key, S: BuildHasher, { - type Resolver = RodeoResolver; + type Resolver = RodeoResolver; #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_resolver(self) -> Self::Resolver where Self: 'static, @@ -98,7 +95,6 @@ where } #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_resolver_boxed(self: Box) -> Self::Resolver where Self: 'static, @@ -107,22 +103,22 @@ where } } -impl Resolver for Rodeo +impl Resolver for Rodeo where K: Key, { #[cfg_attr(feature = "inline-more", inline)] - fn resolve<'a>(&'a self, key: &K) -> &'a str { + fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref { self.resolve(key) } #[cfg_attr(feature = "inline-more", inline)] - fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str> { + fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref> { self.try_resolve(key) } #[cfg_attr(feature = "inline-more", inline)] - unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a str { + unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a T::Ref { unsafe { self.resolve_unchecked(key) } } diff --git a/src/interface/rodeo_reader.rs b/src/interface/rodeo_reader.rs index f528065..393640a 100644 --- a/src/interface/rodeo_reader.rs +++ b/src/interface/rodeo_reader.rs @@ -1,35 +1,34 @@ //! Implementations of [`Reader`] and [`Resolver`] for [`RodeoReader`] -use crate::{IntoResolver, Key, Reader, Resolver, RodeoReader, RodeoResolver}; +use crate::{rodeo::Internable, IntoResolver, Key, Reader, Resolver, RodeoReader, RodeoResolver}; #[cfg(feature = "no-std")] use alloc::boxed::Box; use core::hash::BuildHasher; -impl Reader for RodeoReader +impl Reader for RodeoReader where K: Key, S: BuildHasher, { #[cfg_attr(feature = "inline-more", inline)] - fn get(&self, val: &str) -> Option { + fn get(&self, val: &T::Ref) -> Option { self.get(val) } #[cfg_attr(feature = "inline-more", inline)] - fn contains(&self, val: &str) -> bool { + fn contains(&self, val: &T::Ref) -> bool { self.contains(val) } } -impl IntoResolver for RodeoReader +impl IntoResolver for RodeoReader where K: Key, S: BuildHasher, { - type Resolver = RodeoResolver; + type Resolver = RodeoResolver; #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_resolver(self) -> Self::Resolver where Self: 'static, @@ -38,7 +37,6 @@ where } #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_resolver_boxed(self: Box) -> Self::Resolver where Self: 'static, @@ -47,22 +45,22 @@ where } } -impl Resolver for RodeoReader +impl Resolver for RodeoReader where K: Key, { #[cfg_attr(feature = "inline-more", inline)] - fn resolve<'a>(&'a self, key: &K) -> &'a str { + fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref { self.resolve(key) } #[cfg_attr(feature = "inline-more", inline)] - fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str> { + fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref> { self.try_resolve(key) } #[cfg_attr(feature = "inline-more", inline)] - unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a str { + unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a T::Ref { unsafe { self.resolve_unchecked(key) } } diff --git a/src/interface/rodeo_resolver.rs b/src/interface/rodeo_resolver.rs index 4880671..d065770 100644 --- a/src/interface/rodeo_resolver.rs +++ b/src/interface/rodeo_resolver.rs @@ -1,23 +1,23 @@ //! Implementations of [`Resolver`] for [`RodeoResolver`] -use crate::{Key, Resolver, RodeoResolver}; +use crate::{rodeo::Internable, Key, Resolver, RodeoResolver}; -impl Resolver for RodeoResolver +impl Resolver for RodeoResolver where K: Key, { #[cfg_attr(feature = "inline-more", inline)] - fn resolve<'a>(&'a self, key: &K) -> &'a str { + fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref { self.resolve(key) } #[cfg_attr(feature = "inline-more", inline)] - fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str> { + fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref> { self.try_resolve(key) } #[cfg_attr(feature = "inline-more", inline)] - unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a str { + unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a T::Ref { unsafe { self.resolve_unchecked(key) } } diff --git a/src/interface/tests.rs b/src/interface/tests.rs index bcb819f..f7f2887 100644 --- a/src/interface/tests.rs +++ b/src/interface/tests.rs @@ -11,7 +11,7 @@ compile! { } if #[feature = "no-std"] { - use alloc::{boxed::Box, vec}; + use alloc::{boxed::Box, string::String, vec}; } } @@ -19,8 +19,8 @@ pub(crate) const INTERNED_STRINGS: &[&str] = &["foo", "bar", "baz", "biz", "buzz pub(crate) const UNINTERNED_STRINGS: &[&str] = &["rodeo", "default", "string", "static", "unwrap", "array"]; -fn filled_rodeo() -> Rodeo { - let mut rodeo = Rodeo::default(); +fn filled_rodeo() -> Rodeo { + let mut rodeo = Rodeo::::default(); for string in INTERNED_STRINGS.iter().copied() { rodeo.try_get_or_intern_static(string).unwrap(); } @@ -41,14 +41,26 @@ pub(crate) fn filled_threaded_rodeo() -> ThreadedRodeo { mod interner { use super::*; - pub fn rodeo( - ) -> Box> { + pub fn rodeo() -> Box< + dyn IntoReaderAndResolver< + String, + Spur, + Reader = RodeoReader, + Resolver = RodeoResolver, + >, + > { Box::new(filled_rodeo()) } #[cfg(feature = "multi-threaded")] - pub fn threaded_rodeo( - ) -> Box> { + pub fn threaded_rodeo() -> Box< + dyn IntoReaderAndResolver< + String, + Spur, + Reader = RodeoReader, + Resolver = RodeoResolver, + >, + > { Box::new(filled_threaded_rodeo()) } } @@ -159,16 +171,17 @@ fn interner_implementations() { mod reader { use super::*; - pub fn rodeo() -> Box> { + pub fn rodeo() -> Box>> { Box::new(filled_rodeo()) } - pub fn rodeo_reader() -> Box> { + pub fn rodeo_reader() -> Box>> { Box::new(filled_rodeo().into_reader()) } #[cfg(feature = "multi-threaded")] - pub fn threaded_rodeo() -> Box> { + pub fn threaded_rodeo() -> Box>> + { Box::new(filled_threaded_rodeo()) } } @@ -228,20 +241,20 @@ fn reader_implementations() { mod resolver { use super::*; - pub fn rodeo() -> Box> { + pub fn rodeo() -> Box> { Box::new(filled_rodeo()) } - pub fn rodeo_reader() -> Box> { + pub fn rodeo_reader() -> Box> { Box::new(filled_rodeo().into_reader()) } - pub fn rodeo_resolver() -> Box> { + pub fn rodeo_resolver() -> Box> { Box::new(filled_rodeo().into_resolver()) } #[cfg(feature = "multi-threaded")] - pub fn threaded_rodeo() -> Box> { + pub fn threaded_rodeo() -> Box> { Box::new(filled_threaded_rodeo()) } } diff --git a/src/interface/threaded_ref.rs b/src/interface/threaded_ref.rs index 422acf8..1187195 100644 --- a/src/interface/threaded_ref.rs +++ b/src/interface/threaded_ref.rs @@ -1,26 +1,28 @@ #![cfg(feature = "multi-threaded")] +use crate::rodeo::Internable; use crate::{Interner, Key, ThreadedRodeo}; use core::hash::{BuildHasher, Hash}; -impl Interner for &ThreadedRodeo +impl Interner for &ThreadedRodeo where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { - fn get_or_intern(&mut self, val: &str) -> K { + fn get_or_intern(&mut self, val: &T::Ref) -> K { ThreadedRodeo::get_or_intern(self, val) } - fn try_get_or_intern(&mut self, val: &str) -> crate::LassoResult { + fn try_get_or_intern(&mut self, val: &T::Ref) -> crate::LassoResult { ThreadedRodeo::try_get_or_intern(self, val) } - fn get_or_intern_static(&mut self, val: &'static str) -> K { + fn get_or_intern_static(&mut self, val: &'static T::Ref) -> K { ThreadedRodeo::get_or_intern_static(self, val) } - fn try_get_or_intern_static(&mut self, val: &'static str) -> crate::LassoResult { + fn try_get_or_intern_static(&mut self, val: &'static T::Ref) -> crate::LassoResult { ThreadedRodeo::try_get_or_intern_static(self, val) } } @@ -39,7 +41,7 @@ mod test { .iter() .copied() .enumerate() - .map(|(i, s)| (Spur::try_from_usize(i).unwrap(), s)) + .map(|(i, s)| (Spur::::try_from_usize(i).unwrap(), s)) { assert!(shared_ref1.get(string).is_some()); assert!(shared_ref2.get(string).is_some()); diff --git a/src/interface/threaded_rodeo.rs b/src/interface/threaded_rodeo.rs index 10ac2a9..6d91ae1 100644 --- a/src/interface/threaded_rodeo.rs +++ b/src/interface/threaded_rodeo.rs @@ -1,53 +1,56 @@ //! Implementations of [`Interner`], [`Reader`] and [`Resolver`] for [`ThreadedRodeo`] #![cfg(feature = "multi-threaded")] +use crate::rodeo::Internable; use crate::*; #[cfg(feature = "no-std")] use alloc::boxed::Box; use core::hash::{BuildHasher, Hash}; -impl Interner for ThreadedRodeo +impl Interner for ThreadedRodeo where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { #[cfg_attr(feature = "inline-more", inline)] - fn get_or_intern(&mut self, val: &str) -> K { + fn get_or_intern(&mut self, val: &T::Ref) -> K { (*self).get_or_intern(val) } #[cfg_attr(feature = "inline-more", inline)] - fn try_get_or_intern(&mut self, val: &str) -> LassoResult { + fn try_get_or_intern(&mut self, val: &T::Ref) -> LassoResult { (*self).try_get_or_intern(val) } #[cfg_attr(feature = "inline-more", inline)] - fn get_or_intern_static(&mut self, val: &'static str) -> K { + fn get_or_intern_static(&mut self, val: &'static T::Ref) -> K { (*self).get_or_intern_static(val) } #[cfg_attr(feature = "inline-more", inline)] - fn try_get_or_intern_static(&mut self, val: &'static str) -> LassoResult { + fn try_get_or_intern_static(&mut self, val: &'static T::Ref) -> LassoResult { (*self).try_get_or_intern_static(val) } } -impl IntoReaderAndResolver for ThreadedRodeo +impl IntoReaderAndResolver for ThreadedRodeo where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { } -impl IntoReader for ThreadedRodeo +impl IntoReader for ThreadedRodeo where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { - type Reader = RodeoReader; + type Reader = RodeoReader; #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_reader(self) -> Self::Reader where Self: 'static, @@ -56,7 +59,6 @@ where } #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_reader_boxed(self: Box) -> Self::Reader where Self: 'static, @@ -65,31 +67,32 @@ where } } -impl Reader for ThreadedRodeo +impl Reader for ThreadedRodeo where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { #[cfg_attr(feature = "inline-more", inline)] - fn get(&self, val: &str) -> Option { + fn get(&self, val: &T::Ref) -> Option { self.get(val) } #[cfg_attr(feature = "inline-more", inline)] - fn contains(&self, val: &str) -> bool { + fn contains(&self, val: &T::Ref) -> bool { self.contains(val) } } -impl IntoResolver for ThreadedRodeo +impl IntoResolver for ThreadedRodeo where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { - type Resolver = RodeoResolver; + type Resolver = RodeoResolver; #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_resolver(self) -> Self::Resolver where Self: 'static, @@ -98,7 +101,6 @@ where } #[cfg_attr(feature = "inline-more", inline)] - #[must_use] fn into_resolver_boxed(self: Box) -> Self::Resolver where Self: 'static, @@ -107,25 +109,26 @@ where } } -impl Resolver for ThreadedRodeo +impl Resolver for ThreadedRodeo where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { #[cfg_attr(feature = "inline-more", inline)] - fn resolve<'a>(&'a self, key: &K) -> &'a str { + fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref { self.resolve(key) } #[cfg_attr(feature = "inline-more", inline)] - fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str> { + fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref> { self.try_resolve(key) } /// [`ThreadedRodeo`] does not actually have a `resolve_unchecked()` method, /// so this just forwards to the normal [`ThreadedRodeo::resolve()`] method #[cfg_attr(feature = "inline-more", inline)] - unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a str { + unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a T::Ref { self.resolve(key) } diff --git a/src/keys.rs b/src/keys.rs index 7bcc4ac..b243a1a 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,5 +1,8 @@ +#[cfg(feature = "no-std")] +use alloc::string::String; use core::{ fmt::{self, Debug, Write}, + marker::PhantomData, num::{NonZeroU16, NonZeroU32, NonZeroU8, NonZeroUsize}, }; @@ -22,15 +25,52 @@ pub unsafe trait Key: Copy + Eq { /// /// Internally is a `NonZeroUsize` to allow for space optimizations when stored inside of an [`Option`] /// +/// The type parameter `T` represents the type this key came from (e.g., `String`), providing +/// type safety to prevent using a key from one Rodeo with a different Rodeo. +/// /// [`ReadOnlyLasso`]: crate::ReadOnlyLasso /// [`Option`]: https://doc.rust-lang.org/std/option/enum.Option.html -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct LargeSpur { +#[repr(C)] +pub struct LargeSpur { key: NonZeroUsize, + _marker: PhantomData T>, +} + +impl Clone for LargeSpur { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for LargeSpur {} + +impl PartialEq for LargeSpur { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + } } -impl LargeSpur { +impl Eq for LargeSpur {} + +impl PartialOrd for LargeSpur { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for LargeSpur { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.key.cmp(&other.key) + } +} + +impl core::hash::Hash for LargeSpur { + fn hash(&self, state: &mut H) { + self.key.hash(state); + } +} + +impl LargeSpur { /// Returns the [`NonZeroUsize`] backing the current `LargeSpur` #[cfg_attr(feature = "inline-more", inline)] pub const fn into_inner(self) -> NonZeroUsize { @@ -38,7 +78,7 @@ impl LargeSpur { } } -unsafe impl Key for LargeSpur { +unsafe impl Key for LargeSpur { #[cfg_attr(feature = "inline-more", inline)] fn into_usize(self) -> usize { self.key.get() - 1 @@ -53,6 +93,7 @@ unsafe impl Key for LargeSpur { unsafe { Some(Self { key: NonZeroUsize::new_unchecked(int + 1), + _marker: PhantomData, }) } } else { @@ -61,14 +102,14 @@ unsafe impl Key for LargeSpur { } } -impl Default for LargeSpur { +impl Default for LargeSpur { #[cfg_attr(feature = "inline-more", inline)] fn default() -> Self { Self::try_from_usize(0).unwrap() } } -impl Debug for LargeSpur { +impl Debug for LargeSpur { #[cfg_attr(feature = "inline-more", inline)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("LargeSpur(")?; @@ -81,15 +122,52 @@ impl Debug for LargeSpur { /// /// Internally is a `NonZeroU32` to allow for space optimizations when stored inside of an [`Option`] /// +/// The type parameter `T` represents the type this key came from (e.g., `String`), providing +/// type safety to prevent using a key from one Rodeo with a different Rodeo. +/// /// [`ReadOnlyLasso`]: crate::ReadOnlyLasso /// [`Option`]: https://doc.rust-lang.org/std/option/enum.Option.html -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct Spur { +#[repr(C)] +pub struct Spur { key: NonZeroU32, + _marker: PhantomData T>, } -impl Spur { +impl Clone for Spur { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Spur {} + +impl PartialEq for Spur { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + } +} + +impl Eq for Spur {} + +impl PartialOrd for Spur { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Spur { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.key.cmp(&other.key) + } +} + +impl core::hash::Hash for Spur { + fn hash(&self, state: &mut H) { + self.key.hash(state); + } +} + +impl Spur { /// Returns the [`NonZeroU32`] backing the current `Spur` #[cfg_attr(feature = "inline-more", inline)] pub const fn into_inner(self) -> NonZeroU32 { @@ -97,7 +175,7 @@ impl Spur { } } -unsafe impl Key for Spur { +unsafe impl Key for Spur { #[cfg_attr(feature = "inline-more", inline)] fn into_usize(self) -> usize { self.key.get() as usize - 1 @@ -112,6 +190,7 @@ unsafe impl Key for Spur { unsafe { Some(Self { key: NonZeroU32::new_unchecked(int as u32 + 1), + _marker: PhantomData, }) } } else { @@ -120,14 +199,14 @@ unsafe impl Key for Spur { } } -impl Default for Spur { +impl Default for Spur { #[cfg_attr(feature = "inline-more", inline)] fn default() -> Self { Self::try_from_usize(0).unwrap() } } -impl Debug for Spur { +impl Debug for Spur { #[cfg_attr(feature = "inline-more", inline)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Spur(")?; @@ -140,15 +219,52 @@ impl Debug for Spur { /// /// Internally is a `NonZeroU16` to allow for space optimizations when stored inside of an [`Option`] /// +/// The type parameter `T` represents the type this key came from (e.g., `String`), providing +/// type safety to prevent using a key from one Rodeo with a different Rodeo. +/// /// [`ReadOnlyLasso`]: crate::ReadOnlyLasso /// [`Option`]: https://doc.rust-lang.org/std/option/enum.Option.html -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct MiniSpur { +#[repr(C)] +pub struct MiniSpur { key: NonZeroU16, + _marker: PhantomData T>, } -impl MiniSpur { +impl Clone for MiniSpur { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for MiniSpur {} + +impl PartialEq for MiniSpur { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + } +} + +impl Eq for MiniSpur {} + +impl PartialOrd for MiniSpur { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MiniSpur { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.key.cmp(&other.key) + } +} + +impl core::hash::Hash for MiniSpur { + fn hash(&self, state: &mut H) { + self.key.hash(state); + } +} + +impl MiniSpur { /// Returns the [`NonZeroU16`] backing the current `MiniSpur` #[cfg_attr(feature = "inline-more", inline)] pub const fn into_inner(self) -> NonZeroU16 { @@ -156,7 +272,7 @@ impl MiniSpur { } } -unsafe impl Key for MiniSpur { +unsafe impl Key for MiniSpur { #[cfg_attr(feature = "inline-more", inline)] fn into_usize(self) -> usize { self.key.get() as usize - 1 @@ -171,6 +287,7 @@ unsafe impl Key for MiniSpur { unsafe { Some(Self { key: NonZeroU16::new_unchecked(int as u16 + 1), + _marker: PhantomData, }) } } else { @@ -179,14 +296,14 @@ unsafe impl Key for MiniSpur { } } -impl Default for MiniSpur { +impl Default for MiniSpur { #[cfg_attr(feature = "inline-more", inline)] fn default() -> Self { Self::try_from_usize(0).unwrap() } } -impl Debug for MiniSpur { +impl Debug for MiniSpur { #[cfg_attr(feature = "inline-more", inline)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("MiniSpur(")?; @@ -199,15 +316,52 @@ impl Debug for MiniSpur { /// /// Internally is a `NonZeroU8` to allow for space optimizations when stored inside of an [`Option`] /// +/// The type parameter `T` represents the type this key came from (e.g., `String`), providing +/// type safety to prevent using a key from one Rodeo with a different Rodeo. +/// /// [`ReadOnlyLasso`]: crate::ReadOnlyLasso /// [`Option`]: https://doc.rust-lang.org/std/option/enum.Option.html -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct MicroSpur { +#[repr(C)] +pub struct MicroSpur { key: NonZeroU8, + _marker: PhantomData T>, } -impl MicroSpur { +impl Clone for MicroSpur { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for MicroSpur {} + +impl PartialEq for MicroSpur { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + } +} + +impl Eq for MicroSpur {} + +impl PartialOrd for MicroSpur { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MicroSpur { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.key.cmp(&other.key) + } +} + +impl core::hash::Hash for MicroSpur { + fn hash(&self, state: &mut H) { + self.key.hash(state); + } +} + +impl MicroSpur { /// Returns the [`NonZeroU8`] backing the current `MicroSpur` #[cfg_attr(feature = "inline-more", inline)] pub const fn into_inner(self) -> NonZeroU8 { @@ -215,7 +369,7 @@ impl MicroSpur { } } -unsafe impl Key for MicroSpur { +unsafe impl Key for MicroSpur { #[cfg_attr(feature = "inline-more", inline)] fn into_usize(self) -> usize { self.key.get() as usize - 1 @@ -230,6 +384,7 @@ unsafe impl Key for MicroSpur { unsafe { Some(Self { key: NonZeroU8::new_unchecked(int as u8 + 1), + _marker: PhantomData, }) } } else { @@ -238,14 +393,14 @@ unsafe impl Key for MicroSpur { } } -impl Default for MicroSpur { +impl Default for MicroSpur { #[cfg_attr(feature = "inline-more", inline)] fn default() -> Self { Self::try_from_usize(0).unwrap() } } -impl Debug for MicroSpur { +impl Debug for MicroSpur { #[cfg_attr(feature = "inline-more", inline)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("MicroSpur(")?; @@ -259,6 +414,7 @@ macro_rules! impl_serde { #[cfg(feature = "serialize")] mod __serde { use super::{$($key),*}; + use core::marker::PhantomData; use serde::{ de::{Deserialize, Deserializer}, ser::{Serialize, Serializer}, @@ -266,7 +422,7 @@ macro_rules! impl_serde { use core::num::{$($ty),*}; $( - impl Serialize for $key { + impl Serialize for $key { #[cfg_attr(feature = "inline-more", inline)] fn serialize(&self, serializer: S) -> Result where @@ -276,14 +432,14 @@ macro_rules! impl_serde { } } - impl<'de> Deserialize<'de> for $key { + impl<'de, T> Deserialize<'de> for $key { #[cfg_attr(feature = "inline-more", inline)] fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let key = <$ty>::deserialize(deserializer)?; - Ok(Self { key }) + Ok(Self { key, _marker: PhantomData }) } } )* @@ -303,6 +459,8 @@ macro_rules! impl_deepsize { ($($type:ident),* $(,)?) => { #[cfg(feature = "deepsize")] mod __deepsize { + #[cfg(feature = "no-std")] + use alloc::string::String; use super::{$($type),*}; #[cfg(test)] use super::Key; @@ -310,13 +468,13 @@ macro_rules! impl_deepsize { use core::mem; $( - impl DeepSizeOf for $type { + impl DeepSizeOf for $type { fn deep_size_of_children(&self, _context: &mut Context) -> usize { 0 } fn deep_size_of(&self) -> usize { - mem::size_of::<$type>() + mem::size_of::<$type>() } } )* @@ -325,8 +483,8 @@ macro_rules! impl_deepsize { fn deepsize_implementations() { $( assert_eq!( - mem::size_of::<$type>(), - $type::try_from_usize(0).unwrap().deep_size_of(), + mem::size_of::<$type>(), + <$type>::try_from_usize(0).unwrap().deep_size_of(), ); )* } @@ -353,7 +511,7 @@ macro_rules! impl_abomonation { use std::io::{self, Write}; $( - impl Abomonation for $type { + impl Abomonation for $type { unsafe fn entomb(&self, write: &mut W) -> io::Result<()> { self.key.entomb(write) } @@ -374,7 +532,7 @@ macro_rules! impl_abomonation { $( unsafe { - let base = $type::try_from_usize(0).unwrap(); + let base = <$type>::try_from_usize(0).unwrap(); abomonation::encode(&base, &mut buf).unwrap(); assert_eq!(base, *abomonation::decode(&mut buf [..]).unwrap().0); @@ -398,12 +556,14 @@ impl_abomonation! { #[cfg(test)] mod tests { use super::*; + #[cfg(feature = "no-std")] + use alloc::string::String; #[test] fn large() { - let zero = LargeSpur::try_from_usize(0).unwrap(); - let max = LargeSpur::try_from_usize(usize::MAX - 1).unwrap(); - let default = LargeSpur::default(); + let zero: LargeSpur = LargeSpur::try_from_usize(0).unwrap(); + let max: LargeSpur = LargeSpur::try_from_usize(usize::MAX - 1).unwrap(); + let default: LargeSpur = LargeSpur::default(); assert_eq!(zero.into_usize(), 0); assert_eq!(max.into_usize(), usize::MAX - 1); @@ -412,21 +572,24 @@ mod tests { #[test] fn large_max_returns_none() { - assert_eq!(None, LargeSpur::try_from_usize(usize::MAX)); + assert_eq!( + None::>, + LargeSpur::try_from_usize(usize::MAX) + ); } #[test] #[should_panic] #[cfg(not(miri))] fn large_max_panics() { - LargeSpur::try_from_usize(usize::MAX).unwrap(); + >::try_from_usize(usize::MAX).unwrap(); } #[test] fn spur() { - let zero = Spur::try_from_usize(0).unwrap(); - let max = Spur::try_from_usize(u32::MAX as usize - 1).unwrap(); - let default = Spur::default(); + let zero: Spur = Spur::try_from_usize(0).unwrap(); + let max: Spur = Spur::try_from_usize(u32::MAX as usize - 1).unwrap(); + let default: Spur = Spur::default(); assert_eq!(zero.into_usize(), 0); assert_eq!(max.into_usize(), u32::MAX as usize - 1); @@ -435,21 +598,24 @@ mod tests { #[test] fn spur_returns_none() { - assert_eq!(None, Spur::try_from_usize(u32::MAX as usize)); + assert_eq!( + None::>, + Spur::try_from_usize(u32::MAX as usize) + ); } #[test] #[should_panic] #[cfg(not(miri))] fn spur_panics() { - Spur::try_from_usize(u32::MAX as usize).unwrap(); + >::try_from_usize(u32::MAX as usize).unwrap(); } #[test] fn mini() { - let zero = MiniSpur::try_from_usize(0).unwrap(); - let max = MiniSpur::try_from_usize(u16::MAX as usize - 1).unwrap(); - let default = MiniSpur::default(); + let zero: MiniSpur = MiniSpur::try_from_usize(0).unwrap(); + let max: MiniSpur = MiniSpur::try_from_usize(u16::MAX as usize - 1).unwrap(); + let default: MiniSpur = MiniSpur::default(); assert_eq!(zero.into_usize(), 0); assert_eq!(max.into_usize(), u16::MAX as usize - 1); @@ -458,21 +624,24 @@ mod tests { #[test] fn mini_returns_none() { - assert_eq!(None, MiniSpur::try_from_usize(u16::MAX as usize)); + assert_eq!( + None::>, + MiniSpur::try_from_usize(u16::MAX as usize) + ); } #[test] #[should_panic] #[cfg(not(miri))] fn mini_panics() { - MiniSpur::try_from_usize(u16::MAX as usize).unwrap(); + >::try_from_usize(u16::MAX as usize).unwrap(); } #[test] fn micro() { - let zero = MicroSpur::try_from_usize(0).unwrap(); - let max = MicroSpur::try_from_usize(u8::MAX as usize - 1).unwrap(); - let default = MicroSpur::default(); + let zero: MicroSpur = MicroSpur::try_from_usize(0).unwrap(); + let max: MicroSpur = MicroSpur::try_from_usize(u8::MAX as usize - 1).unwrap(); + let default: MicroSpur = MicroSpur::default(); assert_eq!(zero.into_usize(), 0); assert_eq!(max.into_usize(), u8::MAX as usize - 1); @@ -481,29 +650,53 @@ mod tests { #[test] fn micro_returns_none() { - assert_eq!(None, MicroSpur::try_from_usize(u8::MAX as usize)); + assert_eq!( + None::>, + MicroSpur::try_from_usize(u8::MAX as usize) + ); } #[test] #[should_panic] #[cfg(not(miri))] fn micro_panics() { - MicroSpur::try_from_usize(u8::MAX as usize).unwrap(); + >::try_from_usize(u8::MAX as usize).unwrap(); } #[test] #[cfg(feature = "serialize")] fn all_serialize() { - let large = LargeSpur::try_from_usize(0).unwrap(); + let large: LargeSpur = LargeSpur::try_from_usize(0).unwrap(); let _ = serde_json::to_string(&large).unwrap(); - let normal = Spur::try_from_usize(0).unwrap(); + let normal: Spur = Spur::try_from_usize(0).unwrap(); let _ = serde_json::to_string(&normal).unwrap(); - let mini = MiniSpur::try_from_usize(0).unwrap(); + let mini: MiniSpur = MiniSpur::try_from_usize(0).unwrap(); let _ = serde_json::to_string(&mini).unwrap(); - let micro = MicroSpur::try_from_usize(0).unwrap(); + let micro: MicroSpur = MicroSpur::try_from_usize(0).unwrap(); let _ = serde_json::to_string(µ).unwrap(); } + + /// Ensure that `Option` has the same size as `Key` (niche optimization) + #[test] + fn option_niche_optimization() { + use core::mem::size_of; + + // Option should be the same size as Spur due to NonZero niche + assert_eq!(size_of::>(), size_of::>>()); + assert_eq!( + size_of::>(), + size_of::>>() + ); + assert_eq!( + size_of::>(), + size_of::>>() + ); + assert_eq!( + size_of::>(), + size_of::>>() + ); + } } diff --git a/src/lib.rs b/src/lib.rs index 65a6368..a8cf465 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,7 +58,7 @@ //! ```rust //! use lasso::Rodeo; //! -//! let mut rodeo = Rodeo::default(); +//! let mut rodeo: Rodeo = Rodeo::default(); //! let key = rodeo.get_or_intern("Hello, world!"); //! //! // Easily retrieve the value of a key and find the key for values @@ -149,7 +149,7 @@ //! use lasso::Rodeo; //! //! // Rodeo and ThreadedRodeo are interchangeable here -//! let mut rodeo = Rodeo::default(); +//! let mut rodeo: Rodeo = Rodeo::default(); //! //! let key = rodeo.get_or_intern("Hello, world!"); //! assert_eq!("Hello, world!", rodeo.resolve(&key)); @@ -169,7 +169,7 @@ //! use lasso::Rodeo; //! //! // Rodeo and ThreadedRodeo are interchangeable here -//! let mut rodeo = Rodeo::default(); +//! let mut rodeo: Rodeo = Rodeo::default(); //! //! let key = rodeo.get_or_intern("Hello, world!"); //! assert_eq!("Hello, world!", rodeo.resolve(&key)); @@ -240,7 +240,7 @@ //! # value_out_of_range(); //! //! // And now we're done and can make `Rodeo`s or `ThreadedRodeo`s that use our custom key! -//! let mut rodeo: Rodeo = Rodeo::new(); +//! let mut rodeo: Rodeo = Rodeo::new(); //! let key = rodeo.get_or_intern("It works!"); //! assert_eq!(rodeo.resolve(&key), "It works!"); //! ``` @@ -447,14 +447,13 @@ pub use rodeo::Rodeo; pub use util::{Capacity, Iter, LassoError, LassoErrorKind, LassoResult, MemoryLimits, Strings}; compile! { - if #[all(feature = "multi-threaded", not(feature = "no-std"))] { + // If the `multi-threaded` and `no-std` features are both active, error + if #[all(feature = "multi-threaded", feature = "no-std")] { + compile_error!("The `multi-threaded` and `no-std` features are not supported together"); + } else if #[feature = "multi-threaded"] { mod threaded_rodeo; pub use threaded_rodeo::ThreadedRodeo; - - // If the `multi-threaded` and `no-std` features are both active - } else if #[all(feature = "multi-threaded", feature = "no-std")] { - compile_error!("The `multi-threaded` and `no-std` features are not supported together"); } } diff --git a/src/reader.rs b/src/reader.rs index 8fb667c..26518a3 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -3,6 +3,7 @@ use crate::{ hasher::RandomState, keys::{Key, Spur}, resolver::RodeoResolver, + rodeo::Internable, util::{Iter, Strings}, Rodeo, }; @@ -19,16 +20,16 @@ use hashbrown::HashMap; /// [`Rodeo`]: crate::Rodeo /// [`ThreadedRodeo`]: crate::ThreadedRodeo #[derive(Debug)] -pub struct RodeoReader { +pub struct RodeoReader, S = RandomState> { // The logic behind this arrangement is more heavily documented inside of // `Rodeo` itself - map: HashMap, - hasher: S, - pub(crate) strings: Vec<&'static str>, - __arena: AnyArena, + pub(crate) map: HashMap, + pub(crate) hasher: S, + pub(crate) strings: Vec<&'static T::Ref>, + pub(crate) __arena: AnyArena, } -impl RodeoReader { +impl RodeoReader { /// Creates a new RodeoReader /// /// # Safety @@ -39,8 +40,8 @@ impl RodeoReader { pub(crate) unsafe fn new( map: HashMap, hasher: S, - strings: Vec<&'static str>, - arena: AnyArena, + strings: Vec<&'static T::Ref>, + arena: AnyArena, ) -> Self { Self { map, @@ -58,7 +59,7 @@ impl RodeoReader { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// /// let rodeo = rodeo.into_reader(); @@ -68,13 +69,13 @@ impl RodeoReader { /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn get(&self, val: T) -> Option + pub fn get(&self, val: V) -> Option where - T: AsRef, + V: AsRef, S: BuildHasher, K: Key, { - let string_slice: &str = val.as_ref(); + let string_slice: &T::Ref = val.as_ref(); // Make a hash of the requested string let hash = self.hasher.hash_one(string_slice); @@ -82,7 +83,7 @@ impl RodeoReader { // Get the map's entry that the string should occupy let entry = self.map.raw_entry().from_hash(hash, |key| { // Safety: The index given by `key` will be in bounds of the strings vector - let key_string: &str = unsafe { index_unchecked!(self.strings, key.into_usize()) }; + let key_string: &T::Ref = unsafe { index_unchecked!(self.strings, key.into_usize()) }; // Compare the requested string against the key's string string_slice == key_string @@ -99,7 +100,7 @@ impl RodeoReader { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// /// let rodeo = rodeo.into_reader(); @@ -109,9 +110,9 @@ impl RodeoReader { /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn contains(&self, val: T) -> bool + pub fn contains(&self, val: V) -> bool where - T: AsRef, + V: AsRef, S: BuildHasher, K: Key, { @@ -126,7 +127,7 @@ impl RodeoReader { /// use lasso::Rodeo; /// # use lasso::{Key, Spur}; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// # let key_that_doesnt_exist = Spur::try_from_usize(1000).unwrap(); /// @@ -156,7 +157,7 @@ impl RodeoReader { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// /// let rodeo = rodeo.into_resolver(); @@ -165,7 +166,7 @@ impl RodeoReader { /// /// [`Key`]: crate::Key #[cfg_attr(feature = "inline-more", inline)] - pub fn resolve<'a>(&'a self, key: &K) -> &'a str + pub fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref where K: Key, { @@ -188,7 +189,7 @@ impl RodeoReader { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// /// let rodeo = rodeo.into_resolver(); @@ -197,7 +198,7 @@ impl RodeoReader { /// /// [`Key`]: crate::Key #[cfg_attr(feature = "inline-more", inline)] - pub fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str> + pub fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref> where K: Key, { @@ -226,7 +227,7 @@ impl RodeoReader { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// /// let rodeo = rodeo.into_resolver(); @@ -237,7 +238,7 @@ impl RodeoReader { /// /// [`Key`]: crate::Key #[cfg_attr(feature = "inline-more", inline)] - pub unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a str + pub unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a T::Ref where K: Key, { @@ -252,7 +253,7 @@ impl RodeoReader { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// rodeo.get_or_intern("Documentation often has little hidden bits in it"); /// /// let rodeo = rodeo.into_reader(); @@ -272,7 +273,7 @@ impl RodeoReader { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let rodeo = Rodeo::default(); + /// let rodeo: Rodeo = Rodeo::default(); /// /// let rodeo = rodeo.into_reader(); /// assert!(rodeo.is_empty()); @@ -285,13 +286,13 @@ impl RodeoReader { /// Returns an iterator over the interned strings and their key values #[cfg_attr(feature = "inline-more", inline)] - pub fn iter(&self) -> Iter<'_, K> { + pub fn iter(&self) -> Iter<'_, T, K> { Iter::from_reader(self) } /// Returns an iterator over the interned strings #[cfg_attr(feature = "inline-more", inline)] - pub fn strings(&self) -> Strings<'_, K> { + pub fn strings(&self) -> Strings<'_, T, K> { Strings::from_reader(self) } @@ -304,7 +305,7 @@ impl RodeoReader { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Appear weak when you are strong, and strong when you are weak."); /// let reader_rodeo = rodeo.into_reader(); /// @@ -318,23 +319,23 @@ impl RodeoReader { /// [`RodeoResolver`]: crate::RodeoResolver #[cfg_attr(feature = "inline-more", inline)] #[must_use] - pub fn into_resolver(self) -> RodeoResolver { + pub fn into_resolver(self) -> RodeoResolver { let RodeoReader { strings, __arena, .. } = self; // Safety: The current reader no longer contains references to the strings // in the vec given to RodeoResolver - unsafe { RodeoResolver::new(strings, __arena) } + unsafe { RodeoResolver::::new(strings, __arena) } } } -unsafe impl Sync for RodeoReader {} -unsafe impl Send for RodeoReader {} +unsafe impl Sync for RodeoReader {} +unsafe impl Send for RodeoReader {} -impl<'a, K: Key, S> IntoIterator for &'a RodeoReader { - type Item = (K, &'a str); - type IntoIter = Iter<'a, K>; +impl<'a, T: Internable, K: Key, S> IntoIterator for &'a RodeoReader { + type Item = (K, &'a T::Ref); + type IntoIter = Iter<'a, T, K>; #[cfg_attr(feature = "inline-more", inline)] fn into_iter(self) -> Self::IntoIter { @@ -342,12 +343,12 @@ impl<'a, K: Key, S> IntoIterator for &'a RodeoReader { } } -impl Index for RodeoReader +impl Index for RodeoReader where K: Key, S: BuildHasher, { - type Output = str; + type Output = T::Ref; #[cfg_attr(feature = "inline-more", inline)] fn index(&self, idx: K) -> &Self::Output { @@ -355,25 +356,25 @@ where } } -impl Eq for RodeoReader {} +impl Eq for RodeoReader {} -impl PartialEq for RodeoReader { +impl PartialEq for RodeoReader { #[cfg_attr(feature = "inline-more", inline)] fn eq(&self, other: &Self) -> bool { self.strings == other.strings } } -impl PartialEq> for RodeoReader { +impl PartialEq> for RodeoReader { #[cfg_attr(feature = "inline-more", inline)] - fn eq(&self, other: &RodeoResolver) -> bool { + fn eq(&self, other: &RodeoResolver) -> bool { self.strings == other.strings } } -impl PartialEq> for RodeoReader { +impl PartialEq> for RodeoReader { #[cfg_attr(feature = "inline-more", inline)] - fn eq(&self, other: &Rodeo) -> bool { + fn eq(&self, other: &Rodeo) -> bool { self.strings == other.strings } } @@ -392,7 +393,7 @@ compile! { } #[cfg(feature = "serialize")] -impl Serialize for RodeoReader { +impl Serialize for RodeoReader { #[cfg_attr(feature = "inline-more", inline)] fn serialize(&self, serializer: S) -> Result where @@ -404,7 +405,7 @@ impl Serialize for RodeoReader { } #[cfg(feature = "serialize")] -impl<'de, K: Key, S: BuildHasher + Default> Deserialize<'de> for RodeoReader { +impl<'de, K: Key, S: BuildHasher + Default> Deserialize<'de> for RodeoReader { #[cfg_attr(feature = "inline-more", inline)] fn deserialize(deserializer: D) -> Result where @@ -423,12 +424,12 @@ impl<'de, K: Key, S: BuildHasher + Default> Deserialize<'de> for RodeoReader::new(capacity.bytes, usize::MAX).expect("failed to allocate memory for interner"); for (key, string) in vector.into_iter().enumerate() { let allocated = unsafe { arena - .store_str(&string) + .store_str::(&string) .expect("failed to allocate enough memory") }; @@ -481,10 +482,12 @@ mod tests { #[cfg(feature = "serialize")] use crate::RodeoReader; use crate::{Key, Rodeo, Spur}; + #[cfg(feature = "no-std")] + use alloc::string::String; #[test] fn get() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); let reader = rodeo.into_reader(); @@ -495,7 +498,7 @@ mod tests { #[test] fn resolve() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); let reader = rodeo.into_reader(); @@ -505,13 +508,13 @@ mod tests { #[test] #[should_panic] fn resolve_panics() { - let reader = Rodeo::default().into_reader(); + let reader = Rodeo::::default().into_reader(); reader.resolve(&Spur::try_from_usize(100).unwrap()); } #[test] fn try_resolve() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); let reader = rodeo.into_reader(); @@ -524,7 +527,7 @@ mod tests { #[test] fn resolve_unchecked() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); let reader = rodeo.into_reader(); @@ -535,7 +538,7 @@ mod tests { #[test] fn len() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); rodeo.get_or_intern("A"); rodeo.get_or_intern("B"); rodeo.get_or_intern("C"); @@ -546,7 +549,7 @@ mod tests { #[test] fn empty() { - let rodeo = Rodeo::default(); + let rodeo: Rodeo = Rodeo::default(); let reader = rodeo.into_reader(); assert!(reader.is_empty()); @@ -554,7 +557,7 @@ mod tests { #[test] fn iter() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern("a"); let b = rodeo.get_or_intern("b"); let c = rodeo.get_or_intern("c"); @@ -570,7 +573,7 @@ mod tests { #[test] fn strings() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); rodeo.get_or_intern("a"); rodeo.get_or_intern("b"); rodeo.get_or_intern("c"); @@ -586,13 +589,13 @@ mod tests { #[test] fn drops() { - let rodeo = Rodeo::default(); + let rodeo: Rodeo = Rodeo::default(); let _ = rodeo.into_reader(); } #[test] fn into_resolver() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); let resolver = rodeo.into_reader().into_resolver(); @@ -602,13 +605,13 @@ mod tests { #[test] #[cfg(not(any(feature = "no-std", feature = "ahasher")))] fn debug() { - let reader = Rodeo::default().into_reader(); + let reader = Rodeo::::default().into_reader(); println!("{:?}", reader); } #[test] fn contains() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); rodeo.get_or_intern(""); let resolver = rodeo.into_reader(); @@ -618,7 +621,7 @@ mod tests { #[test] fn contains_key() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern(""); let resolver = rodeo.into_reader(); @@ -646,7 +649,7 @@ mod tests { #[test] fn index() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); let reader = rodeo.into_reader(); @@ -656,7 +659,7 @@ mod tests { #[test] #[cfg(feature = "serialize")] fn empty_serialize() { - let rodeo = Rodeo::default().into_reader(); + let rodeo = Rodeo::::default().into_reader(); let ser = serde_json::to_string(&rodeo).unwrap(); let ser2 = serde_json::to_string(&rodeo).unwrap(); @@ -671,7 +674,7 @@ mod tests { #[test] #[cfg(feature = "serialize")] fn filled_serialize() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern("a"); let b = rodeo.get_or_intern("b"); let c = rodeo.get_or_intern("c"); @@ -702,15 +705,15 @@ mod tests { #[test] fn reader_eq() { - let a = Rodeo::default(); - let b = Rodeo::default(); + let a: Rodeo = Rodeo::default(); + let b: Rodeo = Rodeo::default(); assert_eq!(a.into_reader(), b.into_reader()); - let mut a = Rodeo::default(); + let mut a: Rodeo = Rodeo::default(); a.get_or_intern("a"); a.get_or_intern("b"); a.get_or_intern("c"); - let mut b = Rodeo::default(); + let mut b: Rodeo = Rodeo::default(); b.get_or_intern("a"); b.get_or_intern("b"); b.get_or_intern("c"); @@ -719,15 +722,15 @@ mod tests { #[test] fn resolver_eq() { - let a = Rodeo::default(); - let b = Rodeo::default(); + let a: Rodeo = Rodeo::default(); + let b: Rodeo = Rodeo::default(); assert_eq!(a.into_reader(), b.into_resolver()); - let mut a = Rodeo::default(); + let mut a: Rodeo = Rodeo::default(); a.get_or_intern("a"); a.get_or_intern("b"); a.get_or_intern("c"); - let mut b = Rodeo::default(); + let mut b: Rodeo = Rodeo::default(); b.get_or_intern("a"); b.get_or_intern("b"); b.get_or_intern("c"); @@ -738,8 +741,8 @@ mod tests { #[cfg(all(not(any(miri, feature = "no-std")), feature = "multi-threaded"))] mod multi_threaded { use crate::{Key, RodeoReader, Spur, ThreadedRodeo}; - use std::thread; use std::sync::Arc; + use std::thread; #[test] fn get() { diff --git a/src/resolver.rs b/src/resolver.rs index 7d7e3fa..8fafc43 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -1,6 +1,7 @@ use crate::{ arenas::AnyArena, keys::{Key, Spur}, + rodeo::Internable, util::{Iter, Strings}, Rodeo, RodeoReader, }; @@ -15,19 +16,19 @@ use core::{marker::PhantomData, ops::Index}; /// [`Rodeo`]: crate::Rodeo /// [`ThreadedRodeo`]: crate::ThreadedRodeo #[derive(Debug)] -pub struct RodeoResolver { +pub struct RodeoResolver> { /// Vector of strings mapped to key indexes that allows key to string resolution - pub(crate) strings: Vec<&'static str>, + pub(crate) strings: Vec<&'static T::Ref>, /// The arena that contains all the strings /// /// This is not touched, but *must* be kept since every string in `self.strings` /// points to it - __arena: AnyArena, + __arena: AnyArena, /// The type of the key __key: PhantomData, } -impl RodeoResolver { +impl RodeoResolver { /// Creates a new RodeoResolver /// /// # Safety @@ -35,7 +36,7 @@ impl RodeoResolver { /// The references inside of `strings` must be absolutely unique, meaning /// that no other references to those strings exist /// - pub(crate) unsafe fn new(strings: Vec<&'static str>, arena: AnyArena) -> Self { + pub(crate) unsafe fn new(strings: Vec<&'static T::Ref>, arena: AnyArena) -> Self { Self { strings, __arena: arena, @@ -56,7 +57,7 @@ impl RodeoResolver { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// /// let rodeo = rodeo.into_resolver(); @@ -65,7 +66,7 @@ impl RodeoResolver { /// /// [`Key`]: crate::Key #[cfg_attr(feature = "inline-more", inline)] - pub fn resolve<'a>(&'a self, key: &K) -> &'a str + pub fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref where K: Key, { @@ -89,7 +90,7 @@ impl RodeoResolver { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// /// let rodeo = rodeo.into_resolver(); @@ -98,7 +99,7 @@ impl RodeoResolver { /// /// [`Key`]: crate::Key #[cfg_attr(feature = "inline-more", inline)] - pub fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str> + pub fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref> where K: Key, { @@ -127,7 +128,7 @@ impl RodeoResolver { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// /// let rodeo = rodeo.into_resolver(); @@ -138,7 +139,7 @@ impl RodeoResolver { /// /// [`Key`]: crate::Key #[cfg_attr(feature = "inline-more", inline)] - pub unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a str + pub unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a T::Ref where K: Key, { @@ -153,7 +154,7 @@ impl RodeoResolver { /// use lasso::Rodeo; /// # use lasso::{Key, Spur}; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// # let key_that_doesnt_exist = Spur::try_from_usize(1000).unwrap(); /// @@ -178,7 +179,7 @@ impl RodeoResolver { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// rodeo.get_or_intern("Documentation often has little hidden bits in it"); /// /// let rodeo = rodeo.into_resolver(); @@ -198,7 +199,7 @@ impl RodeoResolver { /// use lasso::Rodeo; /// /// // ThreadedRodeo is interchangeable for Rodeo here - /// let rodeo = Rodeo::default(); + /// let rodeo: Rodeo = Rodeo::default(); /// /// let rodeo = rodeo.into_resolver(); /// assert!(rodeo.is_empty()); @@ -211,23 +212,23 @@ impl RodeoResolver { /// Returns an iterator over the interned strings and their key values #[cfg_attr(feature = "inline-more", inline)] - pub fn iter(&self) -> Iter<'_, K> { + pub fn iter(&self) -> Iter<'_, T, K> { Iter::from_resolver(self) } /// Returns an iterator over the interned strings #[cfg_attr(feature = "inline-more", inline)] - pub fn strings(&self) -> Strings<'_, K> { + pub fn strings(&self) -> Strings<'_, T, K> { Strings::from_resolver(self) } } -unsafe impl Send for RodeoResolver {} -unsafe impl Sync for RodeoResolver {} +unsafe impl Send for RodeoResolver {} +unsafe impl Sync for RodeoResolver {} -impl<'a, K: Key> IntoIterator for &'a RodeoResolver { - type Item = (K, &'a str); - type IntoIter = Iter<'a, K>; +impl<'a, T: Internable, K: Key> IntoIterator for &'a RodeoResolver { + type Item = (K, &'a T::Ref); + type IntoIter = Iter<'a, T, K>; #[cfg_attr(feature = "inline-more", inline)] fn into_iter(self) -> Self::IntoIter { @@ -235,8 +236,8 @@ impl<'a, K: Key> IntoIterator for &'a RodeoResolver { } } -impl Index for RodeoResolver { - type Output = str; +impl Index for RodeoResolver { + type Output = T::Ref; #[cfg_attr(feature = "inline-more", inline)] fn index(&self, idx: K) -> &Self::Output { @@ -244,25 +245,25 @@ impl Index for RodeoResolver { } } -impl Eq for RodeoResolver {} +impl Eq for RodeoResolver {} -impl PartialEq for RodeoResolver { +impl PartialEq for RodeoResolver { #[cfg_attr(feature = "inline-more", inline)] fn eq(&self, other: &Self) -> bool { self.strings == other.strings } } -impl PartialEq> for RodeoResolver { +impl PartialEq> for RodeoResolver { #[cfg_attr(feature = "inline-more", inline)] - fn eq(&self, other: &RodeoReader) -> bool { + fn eq(&self, other: &RodeoReader) -> bool { self.strings == other.strings } } -impl PartialEq> for RodeoResolver { +impl PartialEq> for RodeoResolver { #[cfg_attr(feature = "inline-more", inline)] - fn eq(&self, other: &Rodeo) -> bool { + fn eq(&self, other: &Rodeo) -> bool { self.strings == other.strings } } @@ -280,7 +281,7 @@ compile! { } #[cfg(feature = "serialize")] -impl Serialize for RodeoResolver { +impl Serialize for RodeoResolver { #[cfg_attr(feature = "inline-more", inline)] fn serialize(&self, serializer: S) -> Result where @@ -292,7 +293,7 @@ impl Serialize for RodeoResolver { } #[cfg(feature = "serialize")] -impl<'de, K: Key> Deserialize<'de> for RodeoResolver { +impl<'de, K: Key> Deserialize<'de> for RodeoResolver { #[cfg_attr(feature = "inline-more", inline)] fn deserialize(deserializer: D) -> Result where @@ -309,12 +310,12 @@ impl<'de, K: Key> Deserialize<'de> for RodeoResolver { let mut strings = Vec::with_capacity(capacity.strings); let mut arena = - Arena::new(capacity.bytes, usize::MAX).expect("failed to allocate memory for interner"); + Arena::::new(capacity.bytes, usize::MAX).expect("failed to allocate memory for interner"); for string in vector { let allocated = unsafe { arena - .store_str(&string) + .store_str::(&string) .expect("failed to allocate enough memory") }; @@ -335,10 +336,12 @@ mod tests { #[cfg(feature = "serialize")] use crate::RodeoResolver; use crate::{Key, Rodeo, Spur}; + #[cfg(feature = "no-std")] + use alloc::string::String; #[test] fn resolve() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); let resolver = rodeo.into_resolver(); @@ -349,13 +352,13 @@ mod tests { #[should_panic] #[cfg(not(miri))] fn resolve_out_of_bounds() { - let resolver = Rodeo::default().into_resolver(); + let resolver = Rodeo::::default().into_resolver(); resolver.resolve(&Spur::try_from_usize(10).unwrap()); } #[test] fn try_resolve() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); let resolver = rodeo.into_resolver(); @@ -368,7 +371,7 @@ mod tests { #[test] fn resolve_unchecked() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern("A"); let resolver = rodeo.into_resolver(); @@ -379,7 +382,7 @@ mod tests { #[test] fn len() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); rodeo.get_or_intern("A"); rodeo.get_or_intern("B"); rodeo.get_or_intern("C"); @@ -390,7 +393,7 @@ mod tests { #[test] fn empty() { - let rodeo = Rodeo::default(); + let rodeo: Rodeo = Rodeo::default(); let read_only = rodeo.into_resolver(); assert!(read_only.is_empty()); @@ -398,13 +401,13 @@ mod tests { #[test] fn drops() { - let rodeo = Rodeo::default(); + let rodeo: Rodeo = Rodeo::default(); let _ = rodeo.into_resolver(); } #[test] fn iter() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern("a"); let b = rodeo.get_or_intern("b"); let c = rodeo.get_or_intern("c"); @@ -420,7 +423,7 @@ mod tests { #[test] fn strings() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); rodeo.get_or_intern("a"); rodeo.get_or_intern("b"); rodeo.get_or_intern("c"); @@ -437,13 +440,13 @@ mod tests { #[test] #[cfg(not(feature = "no-std"))] fn debug() { - let resolver = Rodeo::default().into_resolver(); + let resolver = Rodeo::::default().into_resolver(); println!("{:?}", resolver); } #[test] fn contains_key() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern(""); let resolver = rodeo.into_resolver(); @@ -470,7 +473,7 @@ mod tests { #[test] fn index() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); let resolver = rodeo.into_resolver(); @@ -495,7 +498,7 @@ mod tests { #[test] #[cfg(feature = "serialize")] fn filled_serialize() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern("a"); let b = rodeo.get_or_intern("b"); let c = rodeo.get_or_intern("c"); @@ -526,11 +529,11 @@ mod tests { #[test] fn resolver_eq() { - let a = Rodeo::default(); + let a = Rodeo::::default(); let b = Rodeo::default(); assert_eq!(a.into_resolver(), b.into_resolver()); - let mut a = Rodeo::default(); + let mut a = Rodeo::::default(); a.get_or_intern("a"); a.get_or_intern("b"); a.get_or_intern("c"); @@ -543,15 +546,15 @@ mod tests { #[test] fn reader_eq() { - let a = Rodeo::default(); + let a = Rodeo::::default(); let b = Rodeo::default(); assert_eq!(a.into_reader(), b.into_resolver()); - let mut a = Rodeo::default(); + let mut a = Rodeo::::default(); a.get_or_intern("a"); a.get_or_intern("b"); a.get_or_intern("c"); - let mut b = Rodeo::default(); + let mut b = Rodeo::::default(); b.get_or_intern("a"); b.get_or_intern("b"); b.get_or_intern("c"); @@ -562,8 +565,8 @@ mod tests { #[cfg(all(not(any(miri, feature = "no-std")), feature = "multi-threaded"))] mod multi_threaded { use crate::{Key, Spur, ThreadedRodeo}; - use std::thread; use std::sync::Arc; + use std::thread; #[test] fn resolve() { diff --git a/src/rodeo.rs b/src/rodeo.rs index ede6d6d..c920e86 100644 --- a/src/rodeo.rs +++ b/src/rodeo.rs @@ -25,7 +25,102 @@ compile! { } /// The map we use to associate keys to strings by the string's hash -type StringMap = HashMap; +pub(crate) type ValMap = HashMap; + +pub trait InternableRef { + /// The alignment requirement for this type. + const ALIGNMENT: usize; + + fn is_empty(&self) -> bool; + fn as_bytes(&self) -> &[u8]; + /// Returns the number of elements. + fn len(&self) -> usize; + fn empty() -> &'static Self; + + /// Creates a reference from a raw pointer and element count. + /// + /// # Safety + /// The pointer must point to `count` valid elements that were originally created + /// from a valid instance of Self, and be properly aligned. + unsafe fn from_raw_parts<'a>(ptr: *const u8, count: usize) -> &'a Self; +} + +pub trait Internable: core::hash::Hash { + type Ref: InternableRef + + ?Sized + + 'static + + Eq + + PartialEq + + core::hash::Hash + + AsRef; + + fn from_ref(r: &Self::Ref) -> Self; +} + +impl Internable for String { + type Ref = str; + + fn from_ref(r: &Self::Ref) -> Self { + String::from(r) + } +} + +impl InternableRef for str { + const ALIGNMENT: usize = core::mem::align_of::(); + + fn is_empty(&self) -> bool { + str::is_empty(self) + } + fn as_bytes(&self) -> &[u8] { + str::as_bytes(self) + } + fn len(&self) -> usize { + str::len(self) + } + fn empty() -> &'static Self { + "" + } + + unsafe fn from_raw_parts<'a>(ptr: *const u8, count: usize) -> &'a Self { + unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(ptr, count)) } + } +} + +impl Internable for Vec { + type Ref = [T]; + + fn from_ref(r: &Self::Ref) -> Self { + r.to_vec() + } +} + +impl InternableRef for [T] { + const ALIGNMENT: usize = core::mem::align_of::(); + + fn is_empty(&self) -> bool { + <[T]>::is_empty(self) + } + + fn as_bytes(&self) -> &[u8] { + // Safety: Converting a slice of T to a slice of bytes. + // This is safe because we're just viewing the memory as bytes. + unsafe { + core::slice::from_raw_parts(self.as_ptr() as *const u8, core::mem::size_of_val(self)) + } + } + + fn len(&self) -> usize { + <[T]>::len(self) + } + + fn empty() -> &'static Self { + &[] + } + + unsafe fn from_raw_parts<'a>(ptr: *const u8, count: usize) -> &'a Self { + unsafe { core::slice::from_raw_parts(ptr as *const T, count) } + } +} /// A string interner that caches strings quickly with a minimal memory footprint, /// returning a unique key to re-access it with `O(1)` times. @@ -35,7 +130,10 @@ type StringMap = HashMap; /// [`Spur`]: crate::Spur /// [`RandomState`]: https://doc.rust-lang.org/std/collections/hash_map/struct.RandomState.html #[derive(Debug)] -pub struct Rodeo { +pub struct Rodeo, S = RandomState> +where + T: Internable, +{ /// Map that allows `str` -> `key` resolution /// /// This must be a `HashMap` (for now) since `raw_api`s are only available for maps and not sets. @@ -50,18 +148,19 @@ pub struct Rodeo { /// /// This allows us to only store references to the internally allocated strings once, /// which drastically decreases memory usage - map: StringMap, + pub(crate) map: ValMap, /// The hasher of the map. This is stored outside of the map so that we can use /// custom hashing on the keys of the map without the map itself trying to do something else hasher: S, /// Vec that allows `key` -> `str` resolution - pub(crate) strings: Vec<&'static str>, + pub(crate) strings: Vec<&'static T::Ref>, /// The arena that holds all allocated strings - arena: Arena, + arena: Arena, } -impl Rodeo +impl Rodeo where + T: Internable, K: Key, { /// Create a new Rodeo @@ -71,7 +170,7 @@ where /// ```rust /// use lasso::{Rodeo, Spur}; /// - /// let mut rodeo: Rodeo = Rodeo::new(); + /// let mut rodeo: Rodeo = Rodeo::new(); /// let hello = rodeo.get_or_intern("Hello, "); /// let world = rodeo.get_or_intern("World!"); /// @@ -98,7 +197,7 @@ where /// ```rust /// use lasso::{Rodeo, Capacity, Spur}; /// - /// let rodeo: Rodeo = Rodeo::with_capacity(Capacity::for_strings(10)); + /// let rodeo: Rodeo = Rodeo::with_capacity(Capacity::for_strings(10)); /// ``` /// /// [`Capacity`]: crate::Capacity @@ -125,7 +224,7 @@ where /// ```rust /// use lasso::{Rodeo, MemoryLimits, Spur}; /// - /// let rodeo: Rodeo = Rodeo::with_memory_limits(MemoryLimits::for_memory_usage(4096)); + /// let rodeo: Rodeo = Rodeo::with_memory_limits(MemoryLimits::for_memory_usage(4096)); /// ``` /// /// [`MemoryLimits`]: crate::MemoryLimits @@ -152,7 +251,7 @@ where /// ```rust /// use lasso::{Rodeo, MemoryLimits, Spur}; /// - /// let rodeo: Rodeo = Rodeo::with_memory_limits(MemoryLimits::for_memory_usage(4096)); + /// let rodeo: Rodeo = Rodeo::with_memory_limits(MemoryLimits::for_memory_usage(4096)); /// ``` /// /// [`Capacity`]: crate::Capacity @@ -166,8 +265,9 @@ where } } -impl Rodeo +impl Rodeo where + T: Internable, K: Key, S: BuildHasher, { @@ -179,7 +279,7 @@ where /// use lasso::{Spur, Rodeo}; /// use std::collections::hash_map::RandomState; /// - /// let rodeo: Rodeo = Rodeo::with_hasher(RandomState::new()); + /// let rodeo: Rodeo = Rodeo::with_hasher(RandomState::new()); /// ``` /// #[cfg_attr(feature = "inline-more", inline)] @@ -201,7 +301,7 @@ where /// use lasso::{Spur, Capacity, Rodeo}; /// use std::collections::hash_map::RandomState; /// - /// let rodeo: Rodeo = Rodeo::with_capacity_and_hasher(Capacity::for_strings(10), RandomState::new()); + /// let rodeo: Rodeo = Rodeo::with_capacity_and_hasher(Capacity::for_strings(10), RandomState::new()); /// ``` /// /// [`Capacity`]: crate::Capacity @@ -224,7 +324,7 @@ where /// use lasso::{Spur, Capacity, MemoryLimits, Rodeo}; /// use std::collections::hash_map::RandomState; /// - /// let rodeo: Rodeo = Rodeo::with_capacity_memory_limits_and_hasher( + /// let rodeo: Rodeo = Rodeo::with_capacity_memory_limits_and_hasher( /// Capacity::for_strings(10), /// MemoryLimits::for_memory_usage(4096), /// RandomState::new(), @@ -264,7 +364,7 @@ where /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// /// // Interned the string /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); @@ -277,9 +377,9 @@ where /// /// [`Spur`]: crate::Spur #[cfg_attr(feature = "inline-more", inline)] - pub fn get_or_intern(&mut self, val: T) -> K + pub fn get_or_intern(&mut self, val: V) -> K where - T: AsRef, + V: AsRef, { self.try_get_or_intern(val) .expect("Failed to get or intern string") @@ -292,7 +392,7 @@ where /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// /// // Interned the string /// let key = rodeo.try_get_or_intern("Strings of things with wings and dings").unwrap(); @@ -304,9 +404,9 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn try_get_or_intern(&mut self, val: T) -> LassoResult + pub fn try_get_or_intern(&mut self, val: V) -> LassoResult where - T: AsRef, + V: AsRef, { let Self { map, @@ -315,13 +415,13 @@ where arena, } = self; - let string_slice: &str = val.as_ref(); + let string_slice: &T::Ref = val.as_ref(); // Make a hash of the requested string let hash = hasher.hash_one(string_slice); // Get the map's entry that the string should occupy - let key = match get_string_entry_mut(map, strings, hash, string_slice) { + let key = match get_string_entry_mut::(map, strings, hash, string_slice) { // The string already exists, so return its key RawEntryMut::Occupied(entry) => *entry.into_key(), @@ -339,7 +439,7 @@ where strings.push(allocated); // Insert the key with the hash of the string that it points to, reusing the hash we made earlier - insert_string(entry, strings, hasher, hash, key); + insert_string::(entry, strings, hasher, hash, key); key } @@ -363,7 +463,7 @@ where /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// /// // Interned the string /// let key = rodeo.get_or_intern_static("Strings of things with wings and dings"); @@ -375,7 +475,7 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn get_or_intern_static(&mut self, string: &'static str) -> K { + pub fn get_or_intern_static(&mut self, string: &'static T::Ref) -> K { self.try_get_or_intern_static(string) .expect("Failed to get or intern static string") } @@ -389,7 +489,7 @@ where /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// /// // Interned the string /// let key = rodeo.try_get_or_intern_static("Strings of things with wings and dings").unwrap(); @@ -401,7 +501,7 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn try_get_or_intern_static(&mut self, string: &'static str) -> LassoResult { + pub fn try_get_or_intern_static(&mut self, string: &'static T::Ref) -> LassoResult { let Self { map, hasher, @@ -413,7 +513,7 @@ where let hash = hasher.hash_one(string); // Get the map's entry that the string should occupy - let key = match get_string_entry_mut(map, strings, hash, string) { + let key = match get_string_entry_mut::(map, strings, hash, string) { // The string already exists, so return its key RawEntryMut::Occupied(entry) => *entry.into_key(), @@ -427,7 +527,7 @@ where strings.push(string); // Insert the key with the hash of the string that it points to, reusing the hash we made earlier - insert_string(entry, strings, hasher, hash, key); + insert_string::(entry, strings, hasher, hash, key); key } @@ -443,7 +543,7 @@ where /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// assert_eq!(Some(key), rodeo.get("Strings of things with wings and dings")); @@ -452,11 +552,11 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn get(&self, val: T) -> Option + pub fn get(&self, val: V) -> Option where - T: AsRef, + V: AsRef, { - let string_slice: &str = val.as_ref(); + let string_slice: &T::Ref = val.as_ref(); // Make a hash of the requested string let hash = self.hasher.hash_one(string_slice); @@ -466,7 +566,8 @@ where .raw_entry() .from_hash(hash, |key| { // Safety: The index given by `key` will be in bounds of the strings vector - let key_string: &str = unsafe { index_unchecked!(self.strings, key.into_usize()) }; + let key_string: &T::Ref = + unsafe { index_unchecked!(self.strings, key.into_usize()) }; // Compare the requested string against the key's string string_slice == key_string @@ -481,7 +582,7 @@ where /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// assert!(rodeo.contains("Strings of things with wings and dings")); @@ -490,9 +591,9 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn contains(&self, val: T) -> bool + pub fn contains(&self, val: V) -> bool where - T: AsRef, + V: AsRef, { self.get(val).is_some() } @@ -500,18 +601,19 @@ where /// Gets a mutable entry for the given string using its hash #[inline] -fn get_string_entry_mut<'a, K>( - map: &'a mut StringMap, - strings: &[&str], +fn get_string_entry_mut<'a, T, K>( + map: &'a mut ValMap, + strings: &[&T::Ref], hash: u64, - target: &str, + target: &T::Ref, ) -> RawEntryMut<'a, K, (), ()> where + T: Internable, K: Key, { map.raw_entry_mut().from_hash(hash, |key| { // Safety: The index given by `key` will be in bounds of the strings vector - let key_string: &str = unsafe { index_unchecked!(strings, key.into_usize()) }; + let key_string: &T::Ref = unsafe { index_unchecked!(strings, key.into_usize()) }; // Compare the requested string against the key's string target == key_string @@ -520,27 +622,29 @@ where /// Inserts a string into a vacant entry using its given hash #[inline] -fn insert_string( +fn insert_string( entry: RawVacantEntryMut, - strings: &[&str], + strings: &[&'static T::Ref], hasher: &S, hash: u64, key: K, ) where + T: Internable, K: Key, S: BuildHasher, { entry.insert_with_hasher(hash, key, (), |key| { // Safety: The index given by `key` will be in bounds of the strings vector - let key_string: &str = unsafe { index_unchecked!(strings, key.into_usize()) }; + let key_string: &T::Ref = unsafe { index_unchecked!(strings, key.into_usize()) }; // Insert the string with the given hash hasher.hash_one(key_string) }); } -impl Rodeo +impl Rodeo where + T: Internable, K: Key, { /// Returns `true` if the given key exists in the current interner @@ -551,7 +655,7 @@ where /// use lasso::Rodeo; /// # use lasso::{Key, Spur}; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// # let key_that_doesnt_exist = Spur::try_from_usize(1000).unwrap(); /// /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); @@ -576,14 +680,14 @@ where /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn resolve<'a>(&'a self, key: &K) -> &'a str { + pub fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref { // Safety: The call to get_unchecked's safety relies on the Key::into_usize impl // being symmetric and the caller having not fabricated a key. If the impl is sound // and symmetric, then it will succeed, as the usize used to create it is a valid @@ -602,14 +706,14 @@ where /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// assert_eq!(Some("Strings of things with wings and dings"), rodeo.try_resolve(&key)); /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str> { + pub fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref> { // Safety: The call to get_unchecked's safety relies on the Key::into_usize impl // being symmetric and the caller having not fabricated a key. If the impl is sound // and symmetric, then it will succeed, as the usize used to create it is a valid @@ -634,7 +738,7 @@ where /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// /// let key = rodeo.get_or_intern("Strings of things with wings and dings"); /// unsafe { @@ -643,12 +747,15 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a str { + pub unsafe fn resolve_unchecked<'a>(&'a self, key: &K) -> &'a T::Ref { unsafe { self.strings.get_unchecked(key.into_usize()) } } } -impl Rodeo { +impl Rodeo +where + T: Internable, +{ /// Gets the number of interned strings /// /// # Example @@ -656,7 +763,7 @@ impl Rodeo { /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// rodeo.get_or_intern("Documentation often has little hidden bits in it"); /// /// assert_eq!(rodeo.len(), 1); @@ -674,7 +781,7 @@ impl Rodeo { /// ```rust /// use lasso::Rodeo; /// - /// let rodeo = Rodeo::default(); + /// let rodeo: Rodeo = Rodeo::default(); /// assert!(rodeo.is_empty()); /// ``` /// @@ -690,7 +797,7 @@ impl Rodeo { /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Somewhere over the rainbow..."); /// /// // We can see that the interner currently contains one string @@ -721,7 +828,7 @@ impl Rodeo { /// ```rust /// use lasso::{Spur, Capacity, Rodeo}; /// - /// let rodeo: Rodeo = Rodeo::with_capacity(Capacity::for_strings(10)); + /// let rodeo: Rodeo = Rodeo::with_capacity(Capacity::for_strings(10)); /// assert_eq!(rodeo.capacity(), 10); /// ``` /// @@ -734,13 +841,13 @@ impl Rodeo { /// Returns an iterator over the interned strings and their key values #[cfg_attr(feature = "inline-more", inline)] - pub fn iter(&self) -> Iter<'_, K> { + pub fn iter(&self) -> Iter<'_, T, K> { Iter::from_rodeo(self) } /// Returns an iterator over the interned strings #[cfg_attr(feature = "inline-more", inline)] - pub fn strings(&self) -> Strings<'_, K> { + pub fn strings(&self) -> Strings<'_, T, K> { Strings::from_rodeo(self) } @@ -766,7 +873,7 @@ impl Rodeo { } } -impl Rodeo { +impl Rodeo { /// Consumes the current Rodeo, returning a [`RodeoReader`] to allow contention-free access of the interner /// from multiple threads /// @@ -775,7 +882,7 @@ impl Rodeo { /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Appear weak when you are strong, and strong when you are weak."); /// /// let read_only_rodeo = rodeo.into_reader(); @@ -788,7 +895,7 @@ impl Rodeo { /// [`RodeoReader`]: crate::RodeoReader #[cfg_attr(feature = "inline-more", inline)] #[must_use] - pub fn into_reader(self) -> RodeoReader { + pub fn into_reader(self) -> RodeoReader { let Self { map, hasher, @@ -808,7 +915,7 @@ impl Rodeo { /// ```rust /// use lasso::Rodeo; /// - /// let mut rodeo = Rodeo::default(); + /// let mut rodeo: Rodeo = Rodeo::default(); /// let key = rodeo.get_or_intern("Appear weak when you are strong, and strong when you are weak."); /// /// let resolver_rodeo = rodeo.into_resolver(); @@ -821,7 +928,7 @@ impl Rodeo { /// [`RodeoResolver`]: crate::RodeoResolver #[cfg_attr(feature = "inline-more", inline)] #[must_use] - pub fn into_resolver(self) -> RodeoResolver { + pub fn into_resolver(self) -> RodeoResolver { let Rodeo { strings, arena, .. } = self; // Safety: No other references to the strings exist @@ -833,25 +940,25 @@ impl Rodeo { /// /// [`Spur`]: crate::Spur /// [`RandomState`]: index.html#cargo-features -impl Default for Rodeo { +impl Default for Rodeo, RandomState> { #[cfg_attr(feature = "inline-more", inline)] fn default() -> Self { Self::new() } } -unsafe impl Send for Rodeo {} +unsafe impl Send for Rodeo {} -impl FromIterator for Rodeo +impl FromIterator for Rodeo where - Str: AsRef, + Str: AsRef, K: Key, S: BuildHasher + Default, { #[cfg_attr(feature = "inline-more", inline)] - fn from_iter(iter: T) -> Self + fn from_iter(iter: I) -> Self where - T: IntoIterator, + I: IntoIterator, { let iter = iter.into_iter(); let (lower, upper) = iter.size_hint(); @@ -861,19 +968,19 @@ where ); for string in iter { - interner.get_or_intern(string.as_ref()); + interner.get_or_intern(string); } interner } } -impl Index for Rodeo +impl Index for Rodeo where K: Key, S: BuildHasher, { - type Output = str; + type Output = T::Ref; #[cfg_attr(feature = "inline-more", inline)] fn index(&self, idx: K) -> &Self::Output { @@ -881,26 +988,26 @@ where } } -impl Extend for Rodeo +impl Extend for Rodeo where K: Key, S: BuildHasher, - T: AsRef, + It: AsRef, { #[cfg_attr(feature = "inline-more", inline)] fn extend(&mut self, iter: I) where - I: IntoIterator, + I: IntoIterator, { for s in iter { - self.get_or_intern(s.as_ref()); + self.get_or_intern(s); } } } -impl<'a, K: Key, S> IntoIterator for &'a Rodeo { - type Item = (K, &'a str); - type IntoIter = Iter<'a, K>; +impl<'a, T: Internable, K: Key, S> IntoIterator for &'a Rodeo { + type Item = (K, &'a T::Ref); + type IntoIter = Iter<'a, T, K>; #[cfg_attr(feature = "inline-more", inline)] fn into_iter(self) -> Self::IntoIter { @@ -908,38 +1015,39 @@ impl<'a, K: Key, S> IntoIterator for &'a Rodeo { } } -impl Eq for Rodeo {} +impl Eq for Rodeo {} -impl PartialEq for Rodeo { +impl PartialEq for Rodeo { #[cfg_attr(feature = "inline-more", inline)] fn eq(&self, other: &Self) -> bool { self.strings == other.strings } } -impl PartialEq> for Rodeo { +impl PartialEq> for Rodeo { #[cfg_attr(feature = "inline-more", inline)] - fn eq(&self, other: &RodeoReader) -> bool { + fn eq(&self, other: &RodeoReader) -> bool { self.strings == other.strings } } -impl PartialEq> for Rodeo { +impl PartialEq> for Rodeo { #[cfg_attr(feature = "inline-more", inline)] - fn eq(&self, other: &RodeoResolver) -> bool { + fn eq(&self, other: &RodeoResolver) -> bool { self.strings == other.strings } } /// Clones all of the strings in `source` into the given arena, strings vec and string map -fn clone_strings_into( - source: &[&str], - arena: &mut Arena, - strings: &mut Vec<&'static str>, - map: &mut StringMap, +fn clone_strings_into( + source: &[&T::Ref], + arena: &mut Arena, + strings: &mut Vec<&'static T::Ref>, + map: &mut ValMap, hasher: &S, ) -> LassoResult<()> where + T: Internable, K: Key, S: BuildHasher, { @@ -954,11 +1062,11 @@ where let hash = hasher.hash_one(allocated); // Insert the allocated string into the string map - match get_string_entry_mut(map, strings, hash, allocated) { + match get_string_entry_mut::(map, strings, hash, allocated) { RawEntryMut::Vacant(vacant) => { let key = K::try_from_usize(idx) .ok_or_else(|| LassoError::new(LassoErrorKind::KeySpaceExhaustion))?; - insert_string(vacant, strings, hasher, hash, key); + insert_string::(vacant, strings, hasher, hash, key); } RawEntryMut::Occupied(_) => { @@ -970,8 +1078,9 @@ where Ok(()) } -impl Rodeo +impl Rodeo where + T: Internable, K: Key, S: BuildHasher + Clone, { @@ -983,7 +1092,7 @@ where // which will allow us to allocate a single bucket that exactly fits those strings, // minimizing allocations let required_capacity = - NonZeroUsize::new(self.strings.iter().copied().map(str::len).sum::()) + NonZeroUsize::new(self.strings.iter().copied().map(T::Ref::len).sum::()) .unwrap_or(Capacity::default().bytes); // Allocate a new arena to fit all strings in @@ -996,10 +1105,10 @@ where // also inserting the allocated strings into the new map let (mut strings, mut map, hasher) = ( Vec::with_capacity(self.strings.len()), - StringMap::::with_capacity_and_hasher(self.map.len(), ()), + ValMap::::with_capacity_and_hasher(self.map.len(), ()), self.hasher.clone(), ); - clone_strings_into(&self.strings, &mut arena, &mut strings, &mut map, &hasher)?; + clone_strings_into::(&self.strings, &mut arena, &mut strings, &mut map, &hasher)?; Ok(Self { map, @@ -1032,7 +1141,7 @@ where .map_err(|_| LassoError::new(LassoErrorKind::FailedAllocation))?; // Clone the values into the target interner - clone_strings_into( + clone_strings_into::( &source.strings, &mut self.arena, &mut self.strings, @@ -1044,8 +1153,9 @@ where } } -impl Clone for Rodeo +impl Clone for Rodeo where + T: Internable, K: Key, S: BuildHasher + Clone, { @@ -1062,7 +1172,7 @@ where } #[cfg(feature = "serialize")] -impl Serialize for Rodeo { +impl Serialize for Rodeo { #[cfg_attr(feature = "inline-more", inline)] fn serialize(&self, serializer: S) -> Result where @@ -1074,7 +1184,7 @@ impl Serialize for Rodeo { } #[cfg(feature = "serialize")] -impl<'de, K: Key, S: BuildHasher + Default> Deserialize<'de> for Rodeo { +impl<'de, K: Key, S: BuildHasher + Default> Deserialize<'de> for Rodeo { #[cfg_attr(feature = "inline-more", inline)] fn deserialize(deserializer: D) -> Result where @@ -1098,7 +1208,7 @@ impl<'de, K: Key, S: BuildHasher + Default> Deserialize<'de> for Rodeo { for (key, string) in vector.into_iter().enumerate() { let allocated = unsafe { arena - .store_str(&string) + .store_str::(&string) .expect("failed to allocate enough memory") }; @@ -1152,19 +1262,19 @@ mod tests { compile! { if #[feature = "no-std"] { - use alloc::{string::ToString, vec::Vec, boxed::Box}; + use alloc::{string::{String, ToString}, vec::Vec, boxed::Box}; } } #[test] fn new() { - let mut rodeo: Rodeo = Rodeo::new(); + let mut rodeo: Rodeo = Rodeo::new(); rodeo.get_or_intern("Test"); } #[test] fn with_capacity() { - let mut rodeo: Rodeo = Rodeo::with_capacity(Capacity::for_strings(10)); + let mut rodeo: Rodeo = Rodeo::with_capacity(Capacity::for_strings(10)); assert_eq!(rodeo.capacity(), 10); rodeo.get_or_intern("Test"); @@ -1183,13 +1293,13 @@ mod tests { #[test] fn with_hasher() { - let mut rodeo: Rodeo = Rodeo::with_hasher(RandomState::new()); + let mut rodeo: Rodeo = Rodeo::with_hasher(RandomState::new()); let key = rodeo.get_or_intern("Test"); assert_eq!("Test", rodeo.resolve(&key)); #[cfg(not(miri))] { - let mut rodeo: Rodeo = + let mut rodeo: Rodeo = Rodeo::with_hasher(ahash::RandomState::new()); let key = rodeo.get_or_intern("Test"); assert_eq!("Test", rodeo.resolve(&key)); @@ -1198,7 +1308,7 @@ mod tests { #[test] fn with_capacity_and_hasher() { - let mut rodeo: Rodeo = + let mut rodeo: Rodeo = Rodeo::with_capacity_and_hasher(Capacity::for_strings(10), RandomState::new()); assert_eq!(rodeo.capacity(), 10); @@ -1217,10 +1327,11 @@ mod tests { #[cfg(not(miri))] { - let mut rodeo: Rodeo = Rodeo::with_capacity_and_hasher( - Capacity::for_strings(10), - ahash::RandomState::new(), - ); + let mut rodeo: Rodeo = + Rodeo::with_capacity_and_hasher( + Capacity::for_strings(10), + ahash::RandomState::new(), + ); assert_eq!(rodeo.capacity(), 10); rodeo.get_or_intern("Test"); @@ -1240,7 +1351,7 @@ mod tests { #[test] fn get_or_intern() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern("A"); assert_eq!(a, rodeo.get_or_intern("A")); @@ -1253,7 +1364,7 @@ mod tests { #[test] fn try_get_or_intern() { - let mut rodeo: Rodeo = Rodeo::new(); + let mut rodeo: Rodeo = Rodeo::new(); for i in 0..u8::MAX as usize - 1 { rodeo.get_or_intern(i.to_string()); @@ -1268,7 +1379,7 @@ mod tests { #[test] fn get_or_intern_static() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern_static("A"); assert_eq!(a, rodeo.get_or_intern_static("A")); @@ -1282,7 +1393,7 @@ mod tests { #[test] fn try_get_or_intern_static() { let mut strings = Vec::new(); - let mut rodeo: Rodeo = Rodeo::new(); + let mut rodeo: Rodeo = Rodeo::new(); for i in 0..u8::MAX as usize - 1 { let ptr = Box::into_raw(i.to_string().into_boxed_str()); @@ -1306,7 +1417,7 @@ mod tests { #[test] fn get() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); assert_eq!(Some(key), rodeo.get("A")); @@ -1314,7 +1425,7 @@ mod tests { #[test] fn resolve() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); assert_eq!("A", rodeo.resolve(&key)); @@ -1324,13 +1435,13 @@ mod tests { #[should_panic] #[cfg(not(miri))] fn resolve_panics() { - let rodeo = Rodeo::default(); + let rodeo: Rodeo = Rodeo::default(); rodeo.resolve(&Spur::try_from_usize(100).unwrap()); } #[test] fn try_resolve() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); assert_eq!(Some("A"), rodeo.try_resolve(&key)); @@ -1339,7 +1450,7 @@ mod tests { #[test] fn resolve_unchecked() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); unsafe { @@ -1349,7 +1460,7 @@ mod tests { #[test] fn len() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); rodeo.get_or_intern("A"); rodeo.get_or_intern("B"); rodeo.get_or_intern("C"); @@ -1359,14 +1470,14 @@ mod tests { #[test] fn empty() { - let rodeo = Rodeo::default(); + let rodeo: Rodeo = Rodeo::default(); assert!(rodeo.is_empty()); } #[test] fn clone_rodeo() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("Test"); assert_eq!("Test", rodeo.resolve(&key)); @@ -1384,7 +1495,7 @@ mod tests { #[test] fn clone_from_rodeo() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("Test"); assert_eq!("Test", rodeo.resolve(&key)); @@ -1408,12 +1519,12 @@ mod tests { #[test] fn drop_rodeo() { - let _ = Rodeo::default(); + let _ = Rodeo::::default(); } #[test] fn iter() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern("a"); let b = rodeo.get_or_intern("b"); let c = rodeo.get_or_intern("c"); @@ -1427,7 +1538,7 @@ mod tests { #[test] fn strings() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); rodeo.get_or_intern("a"); rodeo.get_or_intern("b"); rodeo.get_or_intern("c"); @@ -1442,14 +1553,14 @@ mod tests { #[test] #[cfg(not(any(feature = "no-std", feature = "ahasher")))] fn debug() { - let rodeo = Rodeo::default(); + let rodeo: Rodeo = Rodeo::default(); println!("{:?}", rodeo); } // Regression test for https://github.com/Kixiron/lasso/issues/7 #[test] fn wrong_keys() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); rodeo.get_or_intern("a"); rodeo.get_or_intern("b"); @@ -1517,7 +1628,7 @@ mod tests { #[test] fn memory_exhausted() { - let mut rodeo: Rodeo = Rodeo::with_capacity_and_memory_limits( + let mut rodeo: Rodeo = Rodeo::with_capacity_and_memory_limits( Capacity::for_bytes(NonZeroUsize::new(10).unwrap()), MemoryLimits::for_memory_usage(10), ); @@ -1536,7 +1647,7 @@ mod tests { #[test] #[should_panic] fn memory_exhausted_panics() { - let mut rodeo: Rodeo = Rodeo::with_capacity_and_memory_limits( + let mut rodeo: Rodeo = Rodeo::with_capacity_and_memory_limits( Capacity::for_bytes(NonZeroUsize::new(10).unwrap()), MemoryLimits::for_memory_usage(10), ); @@ -1549,7 +1660,7 @@ mod tests { #[test] fn with_capacity_memory_limits_and_hasher() { - let mut rodeo: Rodeo = Rodeo::with_capacity_memory_limits_and_hasher( + let mut rodeo: Rodeo = Rodeo::with_capacity_memory_limits_and_hasher( Capacity::default(), MemoryLimits::default(), RandomState::new(), @@ -1560,7 +1671,7 @@ mod tests { #[test] fn with_capacity_and_memory_limits() { - let mut rodeo: Rodeo = + let mut rodeo: Rodeo = Rodeo::with_capacity_and_memory_limits(Capacity::default(), MemoryLimits::default()); rodeo.get_or_intern("Test"); @@ -1568,7 +1679,7 @@ mod tests { #[test] fn set_memory_limits() { - let mut rodeo: Rodeo = Rodeo::with_capacity_and_memory_limits( + let mut rodeo: Rodeo = Rodeo::with_capacity_and_memory_limits( Capacity::for_bytes(NonZeroUsize::new(10).unwrap()), MemoryLimits::for_memory_usage(10), ); @@ -1597,7 +1708,7 @@ mod tests { #[test] fn memory_usage_stats() { - let mut rodeo: Rodeo = Rodeo::with_capacity_and_memory_limits( + let mut rodeo: Rodeo = Rodeo::with_capacity_and_memory_limits( Capacity::for_bytes(NonZeroUsize::new(10).unwrap()), MemoryLimits::for_memory_usage(10), ); @@ -1610,7 +1721,7 @@ mod tests { #[test] fn contains() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); assert!(!rodeo.contains("")); rodeo.get_or_intern(""); @@ -1621,7 +1732,7 @@ mod tests { #[test] fn contains_key() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); assert!(!rodeo.contains("")); let key = rodeo.get_or_intern(""); @@ -1645,7 +1756,7 @@ mod tests { #[test] fn index() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let key = rodeo.get_or_intern("A"); assert_eq!("A", &rodeo[key]); @@ -1653,7 +1764,7 @@ mod tests { #[test] fn extend() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); assert!(rodeo.is_empty()); rodeo.extend(["a", "b", "c", "d", "e"].iter()); @@ -1681,7 +1792,7 @@ mod tests { #[test] #[cfg(feature = "serialize")] fn empty_serialize() { - let rodeo = Rodeo::default(); + let rodeo: Rodeo = Rodeo::default(); let ser = serde_json::to_string(&rodeo).unwrap(); let ser2 = serde_json::to_string(&rodeo).unwrap(); @@ -1696,7 +1807,7 @@ mod tests { #[test] #[cfg(feature = "serialize")] fn filled_serialize() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern("a"); let b = rodeo.get_or_intern("b"); let c = rodeo.get_or_intern("c"); @@ -1726,15 +1837,15 @@ mod tests { #[test] fn rodeo_eq() { - let a = Rodeo::default(); - let b = Rodeo::default(); + let a: Rodeo = Rodeo::default(); + let b: Rodeo = Rodeo::default(); assert_eq!(a, b); - let mut a = Rodeo::default(); + let mut a: Rodeo = Rodeo::default(); a.get_or_intern("a"); a.get_or_intern("b"); a.get_or_intern("c"); - let mut b = Rodeo::default(); + let mut b: Rodeo = Rodeo::default(); b.get_or_intern("a"); b.get_or_intern("b"); b.get_or_intern("c"); @@ -1743,15 +1854,15 @@ mod tests { #[test] fn resolver_eq() { - let a = Rodeo::default(); + let a = Rodeo::::default(); let b = Rodeo::default().into_resolver(); assert_eq!(a, b); - let mut a = Rodeo::default(); + let mut a = Rodeo::::default(); a.get_or_intern("a"); a.get_or_intern("b"); a.get_or_intern("c"); - let mut b = Rodeo::default(); + let mut b = Rodeo::::default(); b.get_or_intern("a"); b.get_or_intern("b"); b.get_or_intern("c"); @@ -1760,11 +1871,11 @@ mod tests { #[test] fn reader_eq() { - let a = Rodeo::default(); + let a = Rodeo::::default(); let b = Rodeo::default().into_reader(); assert_eq!(a, b); - let mut a = Rodeo::default(); + let mut a = Rodeo::::default(); a.get_or_intern("a"); a.get_or_intern("b"); a.get_or_intern("c"); @@ -1774,4 +1885,64 @@ mod tests { b.get_or_intern("c"); assert_eq!(a, b.into_reader()); } + + #[test] + fn intern_zst_vec() { + let mut rodeo: Rodeo> = Rodeo::new(); + + // Intern various lengths of ZST vectors + let empty = rodeo.get_or_intern(&[][..]); + let one = rodeo.get_or_intern(&[()][..]); + let three = rodeo.get_or_intern(&[(), (), ()][..]); + let five = rodeo.get_or_intern(&[(), (), (), (), ()][..]); + + // Verify they resolve correctly + assert_eq!(rodeo.resolve(&empty), &[][..]); + assert_eq!(rodeo.resolve(&one), &[()][..]); + assert_eq!(rodeo.resolve(&three), &[(), (), ()][..]); + assert_eq!(rodeo.resolve(&five), &[(), (), (), (), ()][..]); + + // Verify deduplication works + assert_eq!(empty, rodeo.get_or_intern(&[][..])); + assert_eq!(one, rodeo.get_or_intern(&[()][..])); + assert_eq!(three, rodeo.get_or_intern(&[(), (), ()][..])); + + // Verify lengths are preserved + assert_eq!(rodeo.resolve(&empty).len(), 0); + assert_eq!(rodeo.resolve(&one).len(), 1); + assert_eq!(rodeo.resolve(&three).len(), 3); + assert_eq!(rodeo.resolve(&five).len(), 5); + } + + /// Test that alignment is handled correctly for types with large alignment requirements + #[test] + fn intern_large_alignment() { + #[repr(align(4096))] + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] + struct Chonky(u8); + + let mut rodeo: Rodeo> = Rodeo::new(); + + // Intern some Chonky slices + let a = rodeo.get_or_intern(&[Chonky(1)][..]); + let b = rodeo.get_or_intern(&[Chonky(2), Chonky(3)][..]); + let c = rodeo.get_or_intern(&[Chonky(4), Chonky(5), Chonky(6)][..]); + + // Verify they resolve correctly + assert_eq!(rodeo.resolve(&a), &[Chonky(1)][..]); + assert_eq!(rodeo.resolve(&b), &[Chonky(2), Chonky(3)][..]); + assert_eq!(rodeo.resolve(&c), &[Chonky(4), Chonky(5), Chonky(6)][..]); + + // Verify alignment is correct - the resolved slices should be properly aligned + let resolved_a = rodeo.resolve(&a); + let resolved_b = rodeo.resolve(&b); + let resolved_c = rodeo.resolve(&c); + + assert_eq!(resolved_a.as_ptr() as usize % 4096, 0, "slice a should be 4096-byte aligned"); + assert_eq!(resolved_b.as_ptr() as usize % 4096, 0, "slice b should be 4096-byte aligned"); + assert_eq!(resolved_c.as_ptr() as usize % 4096, 0, "slice c should be 4096-byte aligned"); + + // Verify deduplication still works + assert_eq!(a, rodeo.get_or_intern(&[Chonky(1)][..])); + } } diff --git a/src/threaded_rodeo.rs b/src/threaded_rodeo.rs index 5b72a92..fa13338 100644 --- a/src/threaded_rodeo.rs +++ b/src/threaded_rodeo.rs @@ -1,4 +1,5 @@ use crate::{ + rodeo::Internable, arenas::{AnyArena, LockfreeArena}, hasher::RandomState, keys::{Key, Spur}, @@ -37,23 +38,23 @@ macro_rules! index_unchecked_mut { /// [`Spur`]: crate::Spur /// [`ahash::RandomState`]: https://docs.rs/ahash/0.3.2/ahash/struct.RandomState.html /// [`RandomState`]: index.html#cargo-features -pub struct ThreadedRodeo { +pub struct ThreadedRodeo, S = RandomState> { // TODO: Should this be migrated over to the scheme that `Rodeo` uses for string storage? // Need benchmarks to see the perf impact of two dashmap lookups and see if that's worth // the storage impact of extra string pointers lying around /// Map that allows str to key resolution - map: DashMap<&'static str, K, S>, + map: DashMap<&'static T::Ref, K, S>, /// Map that allows key to str resolution - pub(crate) strings: DashMap, + pub(crate) strings: DashMap, /// The current key value key: AtomicUsize, /// The arena where all strings are stored - arena: LockfreeArena, + arena: LockfreeArena, } // TODO: More parity functions with std::HashMap -impl ThreadedRodeo +impl ThreadedRodeo where K: Key + Hash, { @@ -70,7 +71,7 @@ where /// use lasso::{ThreadedRodeo, Spur}; /// use std::{thread, sync::Arc}; /// - /// let lasso: Arc> = Arc::new(ThreadedRodeo::new()); + /// let lasso: Arc = Arc::new(ThreadedRodeo::new()); /// let hello = lasso.get_or_intern("Hello, "); /// /// let l = Arc::clone(&lasso); @@ -104,7 +105,7 @@ where /// ```rust /// use lasso::{ThreadedRodeo, Capacity, Spur}; /// - /// let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity(Capacity::for_strings(10)); + /// let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity(Capacity::for_strings(10)); /// ``` /// /// [`Capacity`]: crate::Capacity @@ -131,7 +132,7 @@ where /// ```rust /// use lasso::{ThreadedRodeo, MemoryLimits, Spur}; /// - /// let rodeo: ThreadedRodeo = ThreadedRodeo::with_memory_limits(MemoryLimits::for_memory_usage(4096)); + /// let rodeo: ThreadedRodeo = ThreadedRodeo::with_memory_limits(MemoryLimits::for_memory_usage(4096)); /// ``` /// /// [`MemoryLimits`]: crate::MemoryLimits @@ -158,7 +159,7 @@ where /// ```rust /// use lasso::{ThreadedRodeo, MemoryLimits, Spur}; /// - /// let rodeo: ThreadedRodeo = ThreadedRodeo::with_memory_limits(MemoryLimits::for_memory_usage(4096)); + /// let rodeo: ThreadedRodeo = ThreadedRodeo::with_memory_limits(MemoryLimits::for_memory_usage(4096)); /// ``` /// /// [`Capacity`]: crate::Capacity @@ -172,7 +173,7 @@ where } } -impl ThreadedRodeo +impl ThreadedRodeo where K: Key + Hash, S: BuildHasher + Clone, @@ -185,7 +186,7 @@ where /// use lasso::{Spur, ThreadedRodeo}; /// use std::collections::hash_map::RandomState; /// - /// let rodeo: ThreadedRodeo = ThreadedRodeo::with_hasher(RandomState::new()); + /// let rodeo: ThreadedRodeo, RandomState> = ThreadedRodeo::with_hasher(RandomState::new()); /// ``` /// #[cfg_attr(feature = "inline-more", inline)] @@ -207,7 +208,7 @@ where /// use lasso::{Spur, Capacity, ThreadedRodeo}; /// use std::collections::hash_map::RandomState; /// - /// let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_and_hasher(Capacity::for_strings(10), RandomState::new()); + /// let rodeo: ThreadedRodeo, RandomState> = ThreadedRodeo::with_capacity_and_hasher(Capacity::for_strings(10), RandomState::new()); /// ``` /// /// [`Capacity`]: crate::Capacity @@ -230,7 +231,7 @@ where /// use lasso::{Spur, Capacity, MemoryLimits, ThreadedRodeo}; /// use std::collections::hash_map::RandomState; /// - /// let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_memory_limits_and_hasher( + /// let rodeo: ThreadedRodeo, RandomState> = ThreadedRodeo::with_capacity_memory_limits_and_hasher( /// Capacity::for_strings(10), /// MemoryLimits::for_memory_usage(4096), /// RandomState::new(), @@ -252,7 +253,7 @@ where map: DashMap::with_capacity_and_hasher(strings, hash_builder.clone()), strings: DashMap::with_capacity_and_hasher(strings, hash_builder), key: AtomicUsize::new(0), - arena: LockfreeArena::new(bytes, max_memory_usage) + arena: LockfreeArena::::new(bytes, max_memory_usage) .expect("failed to allocate memory for interner"), } } @@ -282,9 +283,9 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn get_or_intern(&self, val: T) -> K + pub fn get_or_intern(&self, val: V) -> K where - T: AsRef, + V: AsRef, { self.try_get_or_intern(val) .expect("Failed to get or intern string") @@ -309,34 +310,33 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn try_get_or_intern(&self, val: T) -> LassoResult + pub fn try_get_or_intern(&self, val: V) -> LassoResult where - T: AsRef, + V: AsRef, { - let string_slice = val.as_ref(); - - if let Some(key) = self.map.get(string_slice) { + let val = val.as_ref(); + if let Some(key) = self.map.get(val) { Ok(*key) } else { - // Determine which shard will have our `string_slice` key. - let hash = self.map.hasher().hash_one(string_slice); + // Determine which shard will have our `val` key. + let hash = self.map.hasher().hash_one(val); let shard_key = self.map.determine_shard(hash as usize); // Grab the shard and a write lock on it. let mut shard = self.map.shards().get(shard_key).unwrap().write(); - // Try getting the value for the `string_slice` key. If we get `Some`, nothing to do. + // Try getting the value for the `val` key. If we get `Some`, nothing to do. // Just return the value, which is the key go to use to resolve the string. If we // get `None`, an entry for the string doesn't exist yet. Store string in the arena, // update the maps accordingly, and return the key. let key = match shard.find_or_find_insert_slot( hash, - |(k, _)| *k == string_slice, + |(k, _)| *k == val, |(k, _)| self.map.hasher().hash_one(k), ) { // Safety: occupied_bucket is valid to borrow, which we keep short Ok(occupied_bucket) => unsafe { *occupied_bucket.as_ref().1.get() }, Err(insert_slot) => { // Safety: The drop impl removes all references before the arena is dropped - let string: &'static str = unsafe { self.arena.store_str(string_slice)? }; + let string: &'static T::Ref = unsafe { self.arena.store_str(val)? }; let key = K::try_from_usize(self.key.fetch_add(1, Ordering::SeqCst)) .ok_or_else(|| LassoError::new(LassoErrorKind::KeySpaceExhaustion))?; @@ -382,8 +382,8 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn get_or_intern_static(&self, string: &'static str) -> K { - self.try_get_or_intern_static(string) + pub fn get_or_intern_static(&self, val: &'static T::Ref) -> K { + self.try_get_or_intern_static(val) .expect("Failed to get or intern static string") } @@ -408,16 +408,16 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn try_get_or_intern_static(&self, string: &'static str) -> LassoResult { - if let Some(key) = self.map.get(string) { + pub fn try_get_or_intern_static(&self, val: &'static T::Ref) -> LassoResult { + if let Some(key) = self.map.get(val) { Ok(*key) } else { - let key = match self.map.entry(string) { + let key = match self.map.entry(val) { Entry::Occupied(o) => *o.get(), Entry::Vacant(v) => { let key = K::try_from_usize(self.key.fetch_add(1, Ordering::SeqCst)) .ok_or_else(|| LassoError::new(LassoErrorKind::KeySpaceExhaustion))?; - self.strings.insert(key, string); + self.strings.insert(key, val); v.insert(key); key @@ -444,9 +444,9 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn get(&self, val: T) -> Option + pub fn get(&self, val: V) -> Option where - T: AsRef, + V: AsRef, { self.map.get(val.as_ref()).map(|k| *k) } @@ -467,9 +467,9 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn contains(&self, val: T) -> bool + pub fn contains(&self, val: V) -> bool where - T: AsRef, + V: AsRef, { self.get(val).is_some() } @@ -514,7 +514,7 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn resolve<'a>(&'a self, key: &K) -> &'a str { + pub fn resolve<'a>(&'a self, key: &K) -> &'a T::Ref { *self.strings.get(key).expect("Key out of bounds") } @@ -533,7 +533,7 @@ where /// ``` /// #[cfg_attr(feature = "inline-more", inline)] - pub fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a str> { + pub fn try_resolve<'a>(&'a self, key: &K) -> Option<&'a T::Ref> { self.strings.get(key).map(|s| *s) } @@ -581,7 +581,7 @@ where /// ```no_run /// use lasso::{Spur, Capacity, ThreadedRodeo}; /// - /// let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity(Capacity::for_strings(10)); + /// let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity(Capacity::for_strings(10)); /// assert_eq!(rodeo.capacity(), 10); /// ``` /// @@ -592,13 +592,13 @@ where /// Returns an iterator over the interned strings and their key values #[cfg_attr(feature = "inline-more", inline)] - pub fn iter(&self) -> Iter<'_, K, S> { + pub fn iter(&self) -> Iter<'_, T, K, S> { Iter::new(self) } /// Returns an iterator over the interned strings #[cfg_attr(feature = "inline-more", inline)] - pub fn strings(&self) -> Strings<'_, K, S> { + pub fn strings(&self) -> Strings<'_, T, K, S> { Strings::new(self) } @@ -645,12 +645,12 @@ where /// [`RodeoReader`]: crate::RodeoReader #[cfg_attr(feature = "inline-more", inline)] #[must_use] - pub fn into_reader(self) -> RodeoReader { + pub fn into_reader(self) -> RodeoReader { // Take the strings vec from the old lasso - let strings: Vec<&'static str> = { + let strings: Vec<&'static T::Ref> = { let mut strings = iter::from_fn(|| Some(None)) .take(self.strings.len()) - .collect::>>(); + .collect::>>(); for shard in self.strings.shards() { for (key, val) in shard.write().drain() { @@ -671,7 +671,7 @@ where for shard in self.map.shards() { for (string, key) in shard.write().drain() { - let string: &str = string; + let string: &T::Ref = string; // Hash the string to use as the key's hash (See `Rodeo`'s documentation for details) let hash = hasher.hash_one(string); @@ -679,7 +679,7 @@ where // Get the entry of the hashmap and insert the key with our new, custom hash let entry = map.raw_entry_mut().from_hash(hash, |key| { // Safety: The index given by `key` will be in bounds of the strings vector - let key_string: &str = + let key_string: &T::Ref = unsafe { index_unchecked!(strings, key.into_usize()) }; // Compare the requested string against the key's string @@ -694,7 +694,7 @@ where RawEntryMut::Vacant(entry) => { // Insert the key with the hash of the string that it points to, reusing the hash we made earlier entry.insert_with_hasher(hash, *key.get(), (), |key| { - let key_string: &str = + let key_string: &T::Ref = unsafe { index_unchecked!(strings, key.into_usize()) }; hasher.hash_one(key_string) @@ -732,10 +732,10 @@ where /// [`RodeoResolver`]: crate::RodeoResolver #[cfg_attr(feature = "inline-more", inline)] #[must_use] - pub fn into_resolver(self) -> RodeoResolver { + pub fn into_resolver(self) -> RodeoResolver { let mut strings = iter::from_fn(|| Some(None)) .take(self.strings.len()) - .collect::>>(); + .collect::>>(); for shard in self.strings.shards() { for (key, val) in shard.write().drain() { @@ -760,15 +760,17 @@ where /// /// [`Spur`]: crate::Spur /// [`RandomState`]: index.html#cargo-features -impl Default for ThreadedRodeo { +impl Default for ThreadedRodeo, RandomState> { #[cfg_attr(feature = "inline-more", inline)] fn default() -> Self { Self::new() } } -impl Debug for ThreadedRodeo +impl Debug for ThreadedRodeo where + T: Internable, + T::Ref: Debug, K: Key + Hash + Debug, S: BuildHasher + Clone, { @@ -782,19 +784,20 @@ where } } -unsafe impl Sync for ThreadedRodeo {} -unsafe impl Send for ThreadedRodeo {} +unsafe impl Sync for ThreadedRodeo where T::Ref: Sync {} +unsafe impl Send for ThreadedRodeo where T::Ref: Send {} -impl FromIterator for ThreadedRodeo +impl FromIterator for ThreadedRodeo where - Str: AsRef, + T: Internable, + Str: AsRef, K: Key + Hash, S: BuildHasher + Clone + Default, { #[cfg_attr(feature = "inline-more", inline)] - fn from_iter(iter: T) -> Self + fn from_iter(iter: I) -> Self where - T: IntoIterator, + I: IntoIterator, { let iter = iter.into_iter(); let (lower, upper) = iter.size_hint(); @@ -804,19 +807,20 @@ where ); for string in iter { - interner.get_or_intern(string.as_ref()); + interner.get_or_intern(string); } interner } } -impl Index for ThreadedRodeo +impl Index for ThreadedRodeo where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { - type Output = str; + type Output = T::Ref; #[cfg_attr(feature = "inline-more", inline)] fn index(&self, idx: K) -> &Self::Output { @@ -824,32 +828,35 @@ where } } -impl Extend for ThreadedRodeo +impl Extend for ThreadedRodeo where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, - T: AsRef, + Str: AsRef, { #[cfg_attr(feature = "inline-more", inline)] fn extend(&mut self, iter: I) where - I: IntoIterator, + I: IntoIterator, { for s in iter { - self.get_or_intern(s.as_ref()); + self.get_or_intern(s); } } } -impl Eq for ThreadedRodeo +impl Eq for ThreadedRodeo where + T: Internable, K: Eq + Hash, S: Clone + BuildHasher, { } -impl PartialEq for ThreadedRodeo +impl PartialEq for ThreadedRodeo where + T: Internable, K: Eq + Hash, S: Clone + BuildHasher, { @@ -866,13 +873,14 @@ where } } -impl PartialEq> for ThreadedRodeo +impl PartialEq> for ThreadedRodeo where + T: Internable, K: Eq + Hash + Key, S: Clone + BuildHasher, { #[cfg_attr(feature = "inline-more", inline)] - fn eq(&self, other: &Rodeo) -> bool { + fn eq(&self, other: &Rodeo) -> bool { self.strings.len() == other.strings.len() && other.strings.iter().enumerate().all(|(key, string)| { K::try_from_usize(key) @@ -883,13 +891,14 @@ where } } -impl PartialEq> for ThreadedRodeo +impl PartialEq> for ThreadedRodeo where + T: Internable, K: Eq + Hash + Key, S: Clone + BuildHasher, { #[cfg_attr(feature = "inline-more", inline)] - fn eq(&self, other: &RodeoReader) -> bool { + fn eq(&self, other: &RodeoReader) -> bool { self.strings.len() == other.strings.len() && other.strings.iter().enumerate().all(|(key, string)| { K::try_from_usize(key) @@ -900,13 +909,14 @@ where } } -impl PartialEq> for ThreadedRodeo +impl PartialEq> for ThreadedRodeo where + T: Internable, K: Eq + Hash + Key, S: Clone + BuildHasher, { #[cfg_attr(feature = "inline-more", inline)] - fn eq(&self, other: &RodeoResolver) -> bool { + fn eq(&self, other: &RodeoResolver) -> bool { self.strings.len() == other.strings.len() && other.strings.iter().enumerate().all(|(key, string)| { K::try_from_usize(key) @@ -929,7 +939,7 @@ compile! { } #[cfg(feature = "serialize")] -impl Serialize for ThreadedRodeo +impl Serialize for ThreadedRodeo where K: Copy + Eq + Hash + Serialize, H: Clone + BuildHasher, @@ -950,7 +960,7 @@ where } #[cfg(feature = "serialize")] -impl<'de, K, S> Deserialize<'de> for ThreadedRodeo +impl<'de, K, S> Deserialize<'de> for ThreadedRodeo where K: Key + Eq + Hash + Deserialize<'de>, S: BuildHasher + Clone + Default, @@ -973,7 +983,7 @@ where let map = DashMap::with_capacity_and_hasher(capacity.strings, hasher.clone()); let strings = DashMap::with_capacity_and_hasher(capacity.strings, hasher); let mut highest = 0; - let arena = LockfreeArena::new(capacity.bytes, usize::MAX) + let arena = LockfreeArena::::new(capacity.bytes, usize::MAX) .expect("failed to allocate memory for interner"); for (string, key) in deser_map { @@ -983,7 +993,7 @@ where let allocated = unsafe { arena - .store_str(&string) + .store_str::(&string) .expect("failed to allocate enough memory") }; @@ -1002,29 +1012,31 @@ where /// An iterator over an interner's strings and keys #[must_use = "iterators are lazy and do nothing unless consumed"] -pub struct Iter<'a, K, S> { - iter: dashmap::iter::Iter<'a, K, &'static str, S, DashMap>, +pub struct Iter<'a, T: Internable, K, S> { + iter: dashmap::iter::Iter<'a, K, &'static T::Ref, S, DashMap>, } -impl<'a, K, S> Iter<'a, K, S> +impl<'a, T, K, S> Iter<'a, T, K, S> where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { #[cfg_attr(feature = "inline-more", inline)] - pub(crate) fn new(rodeo: &'a ThreadedRodeo) -> Self { + pub(crate) fn new(rodeo: &'a ThreadedRodeo) -> Self { Self { iter: rodeo.strings.iter(), } } } -impl<'a, K, S> Iterator for Iter<'a, K, S> +impl<'a, T, K, S> Iterator for Iter<'a, T, K, S> where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { - type Item = (K, &'a str); + type Item = (K, &'a T::Ref); #[cfg_attr(feature = "inline-more", inline)] fn next(&mut self) -> Option { @@ -1037,38 +1049,39 @@ where } } -impl Debug for Iter<'_, K, S> { +impl Debug for Iter<'_, T, K, S> { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { f.debug_struct("Iter").finish_non_exhaustive() } } /// An iterator over an interner's strings -#[derive(Debug)] #[must_use = "iterators are lazy and do nothing unless consumed"] -pub struct Strings<'a, K, S> { - iter: Iter<'a, K, S>, +pub struct Strings<'a, T: Internable, K, S> { + iter: Iter<'a, T, K, S>, } -impl<'a, K, S> Strings<'a, K, S> +impl<'a, T, K, S> Strings<'a, T, K, S> where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { #[cfg_attr(feature = "inline-more", inline)] - pub(crate) fn new(rodeo: &'a ThreadedRodeo) -> Self { + pub(crate) fn new(rodeo: &'a ThreadedRodeo) -> Self { Self { iter: Iter::new(rodeo), } } } -impl<'a, K, S> Iterator for Strings<'a, K, S> +impl<'a, T, K, S> Iterator for Strings<'a, T, K, S> where + T: Internable, K: Key + Hash, S: BuildHasher + Clone, { - type Item = &'a str; + type Item = &'a T::Ref; #[cfg_attr(feature = "inline-more", inline)] fn next(&mut self) -> Option { @@ -1081,6 +1094,12 @@ where } } +impl Debug for Strings<'_, T, K, S> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_struct("Strings").finish_non_exhaustive() + } +} + #[cfg(test)] mod tests { use super::*; @@ -1095,7 +1114,7 @@ mod tests { compile! { if #[feature = "no-std"] { - use alloc::string::ToString; + use alloc::string::{String, ToString}; } else { use std::string::ToString; } @@ -1103,19 +1122,19 @@ mod tests { #[test] fn new() { - let _: ThreadedRodeo = ThreadedRodeo::new(); + let _: ThreadedRodeo = ThreadedRodeo::new(); } #[test] fn with_capacity() { - let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity(Capacity::for_strings(10)); + let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity(Capacity::for_strings(10)); // DashMap's capacity isn't reliable let _cap = rodeo.capacity(); } #[test] fn with_hasher() { - let rodeo: ThreadedRodeo = + let rodeo: ThreadedRodeo, RandomState> = ThreadedRodeo::with_hasher(RandomState::new()); let key = rodeo.get_or_intern("Test"); @@ -1124,7 +1143,7 @@ mod tests { #[test] fn with_capacity_and_hasher() { - let rodeo: ThreadedRodeo = + let rodeo: ThreadedRodeo, RandomState> = ThreadedRodeo::with_capacity_and_hasher(Capacity::for_strings(10), RandomState::new()); let key = rodeo.get_or_intern("Test"); @@ -1174,10 +1193,10 @@ mod tests { #[test] fn try_get_or_intern() { - let rodeo: ThreadedRodeo = ThreadedRodeo::new(); + let rodeo: ThreadedRodeo = ThreadedRodeo::new(); for i in 0..u8::MAX as usize - 1 { - rodeo.get_or_intern(i.to_string()); + rodeo.get_or_intern(&i.to_string()); } let space = rodeo.try_get_or_intern("A").unwrap(); @@ -1191,10 +1210,10 @@ mod tests { #[test] #[cfg(not(any(miri, feature = "no-std")))] fn try_get_or_intern_threaded() { - let rodeo: Arc> = Arc::new(ThreadedRodeo::new()); + let rodeo: Arc> = Arc::new(ThreadedRodeo::new()); for i in 0..u8::MAX as usize - 1 { - rodeo.get_or_intern(i.to_string()); + rodeo.get_or_intern(&i.to_string()); } let moved = Arc::clone(&rodeo); @@ -1248,7 +1267,7 @@ mod tests { #[test] #[cfg(not(any(miri, feature = "no-std")))] fn try_get_or_intern_static_threaded() { - let rodeo: Arc> = Arc::new(ThreadedRodeo::new()); + let rodeo: Arc> = Arc::new(ThreadedRodeo::new()); let moved = Arc::clone(&rodeo); thread::spawn(move || { @@ -1494,7 +1513,7 @@ mod tests { #[test] fn memory_exhausted() { - let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_and_memory_limits( + let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_and_memory_limits( Capacity::for_bytes(NonZeroUsize::new(10).unwrap()), MemoryLimits::for_memory_usage(10), ); @@ -1512,7 +1531,7 @@ mod tests { #[test] #[cfg(not(any(miri, feature = "no-std")))] fn memory_exhausted_threaded() { - let rodeo: Arc> = + let rodeo: Arc = Arc::new(ThreadedRodeo::with_capacity_and_memory_limits( Capacity::for_bytes(NonZeroUsize::new(10).unwrap()), MemoryLimits::for_memory_usage(10), @@ -1544,7 +1563,7 @@ mod tests { #[test] #[should_panic] fn memory_exhausted_panics() { - let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_and_memory_limits( + let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_and_memory_limits( Capacity::for_bytes(NonZeroUsize::new(10).unwrap()), MemoryLimits::for_memory_usage(10), ); @@ -1557,7 +1576,7 @@ mod tests { #[test] fn with_capacity_memory_limits_and_hasher() { - let rodeo: ThreadedRodeo = + let rodeo: ThreadedRodeo, RandomState> = ThreadedRodeo::with_capacity_memory_limits_and_hasher( Capacity::default(), MemoryLimits::default(), @@ -1569,7 +1588,7 @@ mod tests { #[test] fn with_capacity_and_memory_limits() { - let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_and_memory_limits( + let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_and_memory_limits( Capacity::default(), MemoryLimits::default(), ); @@ -1579,7 +1598,7 @@ mod tests { #[test] fn set_memory_limits() { - let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_and_memory_limits( + let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_and_memory_limits( Capacity::for_bytes(NonZeroUsize::new(10).unwrap()), MemoryLimits::for_memory_usage(10), ); @@ -1608,7 +1627,7 @@ mod tests { #[test] fn memory_usage_stats() { - let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_and_memory_limits( + let rodeo: ThreadedRodeo = ThreadedRodeo::with_capacity_and_memory_limits( Capacity::for_bytes(NonZeroUsize::new(10).unwrap()), MemoryLimits::for_memory_usage(10), ); diff --git a/src/util.rs b/src/util.rs index cbcb1cf..141feba 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,9 @@ -use crate::{keys::Key, reader::RodeoReader, resolver::RodeoResolver, rodeo::Rodeo}; +use crate::{ + keys::Key, + reader::RodeoReader, + resolver::RodeoResolver, + rodeo::{Internable, Rodeo}, +}; use core::{fmt, iter, marker::PhantomData, num::NonZeroUsize, slice}; /// A continence type for an error from an interner @@ -196,14 +201,14 @@ impl Default for MemoryLimits { /// An iterator over an interner's strings and keys #[derive(Clone, Debug)] #[must_use = "iterators are lazy and do nothing unless consumed"] -pub struct Iter<'a, K> { - iter: iter::Enumerate>, +pub struct Iter<'a, T: Internable, K> { + iter: iter::Enumerate>, __key: PhantomData, } -impl<'a, K> Iter<'a, K> { +impl<'a, T: Internable, K> Iter<'a, T, K> { #[cfg_attr(feature = "inline-more", inline)] - pub(crate) fn from_rodeo(rodeo: &'a Rodeo) -> Self { + pub(crate) fn from_rodeo(rodeo: &'a Rodeo) -> Self { Self { iter: rodeo.strings.iter().enumerate(), __key: PhantomData, @@ -211,7 +216,7 @@ impl<'a, K> Iter<'a, K> { } #[cfg_attr(feature = "inline-more", inline)] - pub(crate) fn from_reader(rodeo: &'a RodeoReader) -> Self { + pub(crate) fn from_reader(rodeo: &'a RodeoReader) -> Self { Self { iter: rodeo.strings.iter().enumerate(), __key: PhantomData, @@ -219,7 +224,7 @@ impl<'a, K> Iter<'a, K> { } #[cfg_attr(feature = "inline-more", inline)] - pub(crate) fn from_resolver(rodeo: &'a RodeoResolver) -> Self { + pub(crate) fn from_resolver(rodeo: &'a RodeoResolver) -> Self { Self { iter: rodeo.strings.iter().enumerate(), __key: PhantomData, @@ -227,7 +232,7 @@ impl<'a, K> Iter<'a, K> { } } -fn iter_element<'a, K>((key, string): (usize, &&'a str)) -> (K, &'a str) +fn iter_element<'a, T: Internable, K>((key, string): (usize, &&'a T::Ref)) -> (K, &'a T::Ref) where K: Key, { @@ -237,15 +242,15 @@ where ) } -impl<'a, K> Iterator for Iter<'a, K> +impl<'a, T: Internable, K> Iterator for Iter<'a, T, K> where K: Key, { - type Item = (K, &'a str); + type Item = (K, &'a T::Ref); #[cfg_attr(feature = "inline-more", inline)] fn next(&mut self) -> Option { - self.iter.next().map(iter_element) + self.iter.next().map(iter_element::) } #[cfg_attr(feature = "inline-more", inline)] @@ -254,26 +259,26 @@ where } } -impl<'a, K> DoubleEndedIterator for Iter<'a, K> +impl<'a, T: Internable, K> DoubleEndedIterator for Iter<'a, T, K> where K: Key, { #[cfg_attr(feature = "inline-more", inline)] - fn next_back(&mut self) -> Option<(K, &'a str)> { - self.iter.next_back().map(iter_element) + fn next_back(&mut self) -> Option<(K, &'a T::Ref)> { + self.iter.next_back().map(iter_element::) } #[cfg_attr(feature = "inline-more", inline)] - fn nth_back(&mut self, n: usize) -> Option<(K, &'a str)> { - self.iter.nth_back(n).map(iter_element) + fn nth_back(&mut self, n: usize) -> Option<(K, &'a T::Ref)> { + self.iter.nth_back(n).map(iter_element::) } } // iter::Enumerate is exact-size if its underlying iterator is exact-size, which slice::Iter is. -impl<'a, K: Key> ExactSizeIterator for Iter<'a, K> {} +impl<'a, T: Internable, K: Key> ExactSizeIterator for Iter<'a, T, K> {} // iter::Enumerate is fused if its underlying iterator is fused, which slice::Iter is. -impl<'a, K: Key> iter::FusedIterator for Iter<'a, K> {} +impl<'a, T: Internable, K: Key> iter::FusedIterator for Iter<'a, T, K> {} // #[derive(Debug)] // pub struct LockedIter<'a, K: Key> { @@ -300,14 +305,14 @@ impl<'a, K: Key> iter::FusedIterator for Iter<'a, K> {} /// An iterator over an interner's strings #[derive(Clone, Debug)] #[must_use = "iterators are lazy and do nothing unless consumed"] -pub struct Strings<'a, K> { - iter: slice::Iter<'a, &'a str>, +pub struct Strings<'a, T: Internable, K> { + iter: slice::Iter<'a, &'a T::Ref>, __key: PhantomData, } -impl<'a, K> Strings<'a, K> { +impl<'a, T: Internable, K> Strings<'a, T, K> { #[cfg_attr(feature = "inline-more", inline)] - pub(crate) fn from_rodeo(rodeo: &'a Rodeo) -> Self { + pub(crate) fn from_rodeo(rodeo: &'a Rodeo) -> Self { Self { iter: rodeo.strings.iter(), __key: PhantomData, @@ -315,7 +320,7 @@ impl<'a, K> Strings<'a, K> { } #[cfg_attr(feature = "inline-more", inline)] - pub(crate) fn from_reader(rodeo: &'a RodeoReader) -> Self { + pub(crate) fn from_reader(rodeo: &'a RodeoReader) -> Self { Self { iter: rodeo.strings.iter(), __key: PhantomData, @@ -323,7 +328,7 @@ impl<'a, K> Strings<'a, K> { } #[cfg_attr(feature = "inline-more", inline)] - pub(crate) fn from_resolver(rodeo: &'a RodeoResolver) -> Self { + pub(crate) fn from_resolver(rodeo: &'a RodeoResolver) -> Self { Self { iter: rodeo.strings.iter(), __key: PhantomData, @@ -331,8 +336,8 @@ impl<'a, K> Strings<'a, K> { } } -impl<'a, K> Iterator for Strings<'a, K> { - type Item = &'a str; +impl<'a, T: Internable, K> Iterator for Strings<'a, T, K> { + type Item = &'a T::Ref; #[cfg_attr(feature = "inline-more", inline)] fn next(&mut self) -> Option { @@ -345,26 +350,26 @@ impl<'a, K> Iterator for Strings<'a, K> { } } -impl<'a, K> DoubleEndedIterator for Strings<'a, K> +impl<'a, T: Internable, K> DoubleEndedIterator for Strings<'a, T, K> where K: Key, { #[cfg_attr(feature = "inline-more", inline)] - fn next_back(&mut self) -> Option<&'a str> { + fn next_back(&mut self) -> Option<&'a T::Ref> { self.iter.next_back().copied() } #[cfg_attr(feature = "inline-more", inline)] - fn nth_back(&mut self, n: usize) -> Option<&'a str> { + fn nth_back(&mut self, n: usize) -> Option<&'a T::Ref> { self.iter.nth_back(n).copied() } } // slice::Iter is exact-size. -impl<'a, K> ExactSizeIterator for Strings<'a, K> {} +impl<'a, T: Internable, K> ExactSizeIterator for Strings<'a, T, K> {} // slice::Iter is fused. -impl<'a, K: Key> iter::FusedIterator for Strings<'a, K> {} +impl<'a, T: Internable, K> iter::FusedIterator for Strings<'a, T, K> {} macro_rules! compile { ($( @@ -478,7 +483,7 @@ mod tests { #[test] fn iter_rodeo() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern("A"); let b = rodeo.get_or_intern("B"); let c = rodeo.get_or_intern("C"); @@ -497,7 +502,7 @@ mod tests { #[test] fn iter_reader() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern("A"); let b = rodeo.get_or_intern("B"); let c = rodeo.get_or_intern("C"); @@ -517,7 +522,7 @@ mod tests { #[test] fn iter_resolver() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); let a = rodeo.get_or_intern("A"); let b = rodeo.get_or_intern("B"); let c = rodeo.get_or_intern("C"); @@ -537,7 +542,7 @@ mod tests { #[test] fn strings_rodeo() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); rodeo.get_or_intern("A"); rodeo.get_or_intern("B"); rodeo.get_or_intern("C"); @@ -556,7 +561,7 @@ mod tests { #[test] fn strings_reader() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); rodeo.get_or_intern("A"); rodeo.get_or_intern("B"); rodeo.get_or_intern("C"); @@ -576,7 +581,7 @@ mod tests { #[test] fn strings_resolver() { - let mut rodeo = Rodeo::default(); + let mut rodeo: Rodeo = Rodeo::default(); rodeo.get_or_intern("A"); rodeo.get_or_intern("B"); rodeo.get_or_intern("C"); diff --git a/tests/static_rodeo.rs b/tests/static_rodeo.rs index beac89e..44a29fd 100644 --- a/tests/static_rodeo.rs +++ b/tests/static_rodeo.rs @@ -2,7 +2,7 @@ use lasso::Rodeo; use std::sync::RwLock; lazy_static::lazy_static! { - static ref INTERNER: RwLock = RwLock::new(Rodeo::new()); + static ref INTERNER: RwLock> = RwLock::new(Rodeo::::new()); } #[test]