diff --git a/src/lib.rs b/src/lib.rs index 2a9c6206..38993e90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,7 @@ use core::fmt; use core::hash::{BuildHasher, Hash, Hasher}; use core::iter::FromIterator; use core::ops::{BitAnd, BitOr, Shl, Shr, Sub}; +use core::ptr; use crossbeam_utils::CachePadded; pub use equivalent::Equivalent; use hashbrown::hash_table; @@ -127,6 +128,26 @@ impl<'a, K: 'a + Eq + Hash, V: 'a> DashMap { DashMap::with_hasher(RandomState::default()) } + /// Creates a new DashMap with a capacity of 0. + /// + /// This is the fallible variant of [`Self::new`]. + /// + /// # Errors + /// + /// Returns an error if the allocation fails. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashMap; + /// + /// let reviews = DashMap::try_new().unwrap(); + /// reviews.insert("Veloren", "What a fantastic game!"); + /// ``` + pub fn try_new() -> Result { + DashMap::try_with_hasher(RandomState::default()) + } + /// Creates a new DashMap with a specified starting capacity. /// /// # Examples @@ -142,6 +163,27 @@ impl<'a, K: 'a + Eq + Hash, V: 'a> DashMap { DashMap::with_capacity_and_hasher(capacity, RandomState::default()) } + /// Creates a new DashMap with a specified starting capacity. + /// + /// This is the fallible variant of [`Self::with_capacity`]. + /// + /// # Errors + /// + /// Returns an error if the allocation fails or the capacity overflows. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashMap; + /// + /// let mappings = DashMap::try_with_capacity(2).unwrap(); + /// mappings.insert(2, 4); + /// mappings.insert(8, 16); + /// ``` + pub fn try_with_capacity(capacity: usize) -> Result { + DashMap::try_with_capacity_and_hasher(capacity, RandomState::default()) + } + /// Creates a new DashMap with a specified shard amount /// /// shard_amount should greater than 0 and be a power of two. @@ -160,6 +202,29 @@ impl<'a, K: 'a + Eq + Hash, V: 'a> DashMap { Self::with_capacity_and_hasher_and_shard_amount(0, RandomState::default(), shard_amount) } + /// Creates a new DashMap with a specified shard amount. + /// + /// This is the fallible variant of [`Self::with_shard_amount`]. + /// + /// shard_amount must be greater than 1 and a power of two. + /// + /// # Errors + /// + /// Returns an error if `shard_amount` is invalid or if the allocation fails. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashMap; + /// + /// let mappings = DashMap::try_with_shard_amount(32).unwrap(); + /// mappings.insert(2, 4); + /// mappings.insert(8, 16); + /// ``` + pub fn try_with_shard_amount(shard_amount: usize) -> Result { + Self::try_with_capacity_and_hasher_and_shard_amount(0, RandomState::default(), shard_amount) + } + /// Creates a new DashMap with a specified capacity and shard amount. /// /// shard_amount should greater than 0 and be a power of two. @@ -181,6 +246,37 @@ impl<'a, K: 'a + Eq + Hash, V: 'a> DashMap { shard_amount, ) } + + /// Creates a new DashMap with a specified capacity and shard amount. + /// + /// This is the fallible variant of [`Self::with_capacity_and_shard_amount`]. + /// + /// shard_amount must be greater than 1 and a power of two. + /// + /// # Errors + /// + /// Returns an error if `shard_amount` is invalid, if the capacity overflows, + /// or if the allocation fails. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashMap; + /// + /// let mappings = DashMap::try_with_capacity_and_shard_amount(32, 32).unwrap(); + /// mappings.insert(2, 4); + /// mappings.insert(8, 16); + /// ``` + pub fn try_with_capacity_and_shard_amount( + capacity: usize, + shard_amount: usize, + ) -> Result { + Self::try_with_capacity_and_hasher_and_shard_amount( + capacity, + RandomState::default(), + shard_amount, + ) + } } impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { @@ -205,6 +301,28 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { Self::with_capacity_and_hasher(0, hasher) } + /// Creates a new DashMap with a capacity of 0 and the provided hasher. + /// + /// This is the fallible variant of [`Self::with_hasher`]. + /// + /// # Errors + /// + /// Returns an error if the allocation fails. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashMap; + /// use std::collections::hash_map::RandomState; + /// + /// let s = RandomState::new(); + /// let reviews = DashMap::try_with_hasher(s).unwrap(); + /// reviews.insert("Veloren", "What a fantastic game!"); + /// ``` + pub fn try_with_hasher(hasher: S) -> Result { + Self::try_with_capacity_and_hasher(0, hasher) + } + /// Creates a new DashMap with a specified starting capacity and hasher. /// /// # Examples @@ -222,6 +340,36 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { Self::with_capacity_and_hasher_and_shard_amount(capacity, hasher, default_shard_amount()) } + /// Creates a new DashMap with a specified starting capacity and hasher. + /// + /// This is the fallible variant of [`Self::with_capacity_and_hasher`]. + /// + /// # Errors + /// + /// Returns an error if the allocation fails or the capacity overflows. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashMap; + /// use std::collections::hash_map::RandomState; + /// + /// let s = RandomState::new(); + /// let mappings = DashMap::try_with_capacity_and_hasher(2, s).unwrap(); + /// mappings.insert(2, 4); + /// mappings.insert(8, 16); + /// ``` + pub fn try_with_capacity_and_hasher( + capacity: usize, + hasher: S, + ) -> Result { + Self::try_with_capacity_and_hasher_and_shard_amount( + capacity, + hasher, + default_shard_amount(), + ) + } + /// Creates a new DashMap with a specified hasher and shard amount /// /// shard_amount should be greater than 0 and a power of two. @@ -242,11 +390,44 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { Self::with_capacity_and_hasher_and_shard_amount(0, hasher, shard_amount) } + /// Creates a new DashMap with a specified hasher and shard amount. + /// + /// This is the fallible variant of [`Self::with_hasher_and_shard_amount`]. + /// + /// shard_amount must be greater than 1 and a power of two. + /// + /// # Errors + /// + /// Returns an error if `shard_amount` is invalid or if the allocation fails. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashMap; + /// use std::collections::hash_map::RandomState; + /// + /// let s = RandomState::new(); + /// let mappings = DashMap::try_with_hasher_and_shard_amount(s, 32).unwrap(); + /// mappings.insert(2, 4); + /// mappings.insert(8, 16); + /// ``` + pub fn try_with_hasher_and_shard_amount( + hasher: S, + shard_amount: usize, + ) -> Result { + Self::try_with_capacity_and_hasher_and_shard_amount(0, hasher, shard_amount) + } + /// Creates a new DashMap with a specified starting capacity, hasher and shard_amount. /// /// shard_amount should greater than 0 and be a power of two. /// If a shard_amount which is not a power of two is provided, the function will panic. /// + /// # Panics + /// + /// Panics if `shard_amount` is not greater than 1 or not a power of two, + /// or if allocation fails. + /// /// # Examples /// /// ``` @@ -285,6 +466,81 @@ impl<'a, K: 'a + Eq + Hash, V: 'a, S: BuildHasher + Clone> DashMap { } } + /// Creates a new DashMap with a specified starting capacity, hasher and shard_amount. + /// + /// This is the fallible variant of [`Self::with_capacity_and_hasher_and_shard_amount`]. + /// + /// shard_amount must be greater than 1 and a power of two. + /// + /// # Errors + /// + /// Returns an error if `shard_amount` is invalid, if the capacity overflows, + /// or if the allocation fails. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashMap; + /// use std::collections::hash_map::RandomState; + /// + /// let s = RandomState::new(); + /// let mappings = DashMap::try_with_capacity_and_hasher_and_shard_amount(2, s, 32).unwrap(); + /// mappings.insert(2, 4); + /// mappings.insert(8, 16); + /// ``` + pub fn try_with_capacity_and_hasher_and_shard_amount( + capacity: usize, + hasher: S, + shard_amount: usize, + ) -> Result { + if shard_amount <= 1 || !shard_amount.is_power_of_two() { + return Err(TryReserveError {}); + } + + let ptr_bits = util::ptr_size_bits(); + let nc = ncb(shard_amount); + let shift = ptr_bits.checked_sub(nc).ok_or(TryReserveError {})?; + + let rounded_capacity = if capacity != 0 { + let mask = shard_amount.checked_sub(1).ok_or(TryReserveError {})?; + capacity.checked_add(mask).ok_or(TryReserveError {})? & !mask + } else { + 0 + }; + + let cps = rounded_capacity / shard_amount; + + let mut guard = + util::InitSliceGuard::>>>::new(shard_amount) + .ok_or(TryReserveError {})?; + + for i in 0..shard_amount { + let mut map = HashMap::new(); + map.try_reserve(cps, |(k, _v): &(K, V)| { + let mut h = hasher.build_hasher(); + k.hash(&mut h); + h.finish() + }) + .map_err(|_| TryReserveError {})?; + + let shard = CachePadded::new(RwLock::new(map)); + // SAFETY: i < shard_amount (loop bound), slot is uninitialized. + unsafe { + ptr::write(guard.get(i), shard); + } + guard.mark_init(); + } + + // SAFETY: all `shard_amount` slots have been initialized by the loop above. + let shards = unsafe { guard.assume_init() }; + + Ok(Self { + shift, + shards, + hasher, + }) + } + /// Hash a given item to produce a usize. /// Uses the provided or default HashBuilder. pub fn hash_usize(&self, item: &T) -> usize { @@ -1427,7 +1683,7 @@ where #[cfg(test)] mod tests { use crate::DashMap; - use std::collections::hash_map::RandomState; + use std::{collections::hash_map::RandomState, usize}; #[test] fn test_basic() { @@ -1602,4 +1858,12 @@ mod tests { _ => panic!("should have raised CapacityOverflow error"), } } + + #[test] + fn test_try_with_capacity_errors() { + match DashMap::::try_with_capacity(usize::MAX) { + Err(_) => {} + _ => panic!("should have raised CapacityOverflow error"), + } + } } diff --git a/src/set.rs b/src/set.rs index 6165ed84..76853884 100644 --- a/src/set.rs +++ b/src/set.rs @@ -2,9 +2,9 @@ use crate::iter_set::{Iter, OwningIter}; #[cfg(feature = "raw-api")] use crate::lock::RwLock; use crate::setref::one::Ref; -use crate::DashMap; #[cfg(feature = "raw-api")] use crate::HashMap; +use crate::{DashMap, TryReserveError}; use cfg_if::cfg_if; use core::fmt; use core::hash::{BuildHasher, Hash}; @@ -65,7 +65,27 @@ impl<'a, K: 'a + Eq + Hash> DashSet { Self::with_hasher(RandomState::default()) } - /// Creates a new DashMap with a specified starting capacity. + /// Creates a new DashSet with a capacity of 0. + /// + /// This is the fallible variant of [`Self::new`]. + /// + /// # Errors + /// + /// Returns an error if the allocation fails. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashSet; + /// + /// let games = DashSet::try_new().unwrap(); + /// games.insert("Veloren"); + /// ``` + pub fn try_new() -> Result { + Self::try_with_hasher(RandomState::default()) + } + + /// Creates a new DashSet with a specified starting capacity. /// /// # Examples /// @@ -79,6 +99,27 @@ impl<'a, K: 'a + Eq + Hash> DashSet { pub fn with_capacity(capacity: usize) -> Self { Self::with_capacity_and_hasher(capacity, RandomState::default()) } + + /// Creates a new DashSet with a specified starting capacity. + /// + /// This is the fallible variant of [`Self::with_capacity`]. + /// + /// # Errors + /// + /// Returns an error if the allocation fails or the capacity overflows. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashSet; + /// + /// let numbers = DashSet::try_with_capacity(2).unwrap(); + /// numbers.insert(2); + /// numbers.insert(8); + /// ``` + pub fn try_with_capacity(capacity: usize) -> Result { + Self::try_with_capacity_and_hasher(capacity, RandomState::default()) + } } impl<'a, K: 'a + Eq + Hash, S: BuildHasher + Clone> DashSet { @@ -98,7 +139,29 @@ impl<'a, K: 'a + Eq + Hash, S: BuildHasher + Clone> DashSet { Self::with_capacity_and_hasher(0, hasher) } - /// Creates a new DashMap with a specified starting capacity and hasher. + /// Creates a new DashSet with a capacity of 0 and the provided hasher. + /// + /// This is the fallible variant of [`Self::with_hasher`]. + /// + /// # Errors + /// + /// Returns an error if the allocation fails. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashSet; + /// use std::collections::hash_map::RandomState; + /// + /// let s = RandomState::new(); + /// let games = DashSet::try_with_hasher(s).unwrap(); + /// games.insert("Veloren"); + /// ``` + pub fn try_with_hasher(hasher: S) -> Result { + Self::try_with_capacity_and_hasher(0, hasher) + } + + /// Creates a new DashSet with a specified starting capacity and hasher. /// /// # Examples /// @@ -117,6 +180,50 @@ impl<'a, K: 'a + Eq + Hash, S: BuildHasher + Clone> DashSet { } } + /// Creates a new DashSet with a specified starting capacity and hasher. + /// + /// This is the fallible variant of [`Self::with_capacity_and_hasher`]. + /// + /// # Errors + /// + /// Returns an error if the allocation fails or the capacity overflows. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashSet; + /// use std::collections::hash_map::RandomState; + /// + /// let s = RandomState::new(); + /// let numbers = DashSet::try_with_capacity_and_hasher(2, s).unwrap(); + /// numbers.insert(2); + /// numbers.insert(8); + /// ``` + pub fn try_with_capacity_and_hasher( + capacity: usize, + hasher: S, + ) -> Result { + Ok(Self { + inner: DashMap::try_with_capacity_and_hasher(capacity, hasher)?, + }) + } + + /// Returns a reference to the set's hasher. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashSet; + /// use std::collections::hash_map::RandomState; + /// + /// let s = RandomState::new(); + /// let set: DashSet = DashSet::with_hasher(s); + /// let _: &RandomState = set.hasher(); + /// ``` + pub fn hasher(&self) -> &S { + self.inner.hasher() + } + /// Hash a given item to produce a usize. /// Uses the provided or default HashBuilder. pub fn hash_usize(&self, item: &T) -> usize { @@ -381,6 +488,80 @@ impl<'a, K: 'a + Eq + Hash, S: BuildHasher + Clone> DashSet { { self.inner.contains_key(key) } + + /// Returns a reference to the inner [`DashMap`]. + /// + /// This allows access to any [`DashMap`] method that isn't exposed by [`DashSet`], + /// such as [`DashMap::get_mut`], [`DashMap::entry`], or [`DashMap::try_get`]. + /// The values in the returned map are always `()`. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashSet; + /// + /// let set = DashSet::new(); + /// set.insert(1); + /// + /// // Use DashMap's entry API through the set + /// let map = set.as_dash_map(); + /// map.entry(2).or_insert(()); + /// assert!(set.contains(&2)); + /// ``` + pub fn as_dash_map(&self) -> &DashMap { + &self.inner + } + + /// Consumes the set and returns the inner [`DashMap`]. + /// + /// The values in the returned map are always `()`. + /// + /// # Examples + /// + /// ``` + /// use dashmap::DashSet; + /// + /// let set = DashSet::new(); + /// set.insert(1); + /// set.insert(2); + /// + /// let map = set.into_dash_map(); + /// assert_eq!(map.len(), 2); + /// ``` + pub fn into_dash_map(self) -> DashMap { + self.inner + } + + /// Creates a [`DashSet`] from a [`DashMap`] whose value type is `()`. + /// + /// # Examples + /// + /// ``` + /// use dashmap::{DashMap, DashSet}; + /// + /// let mut map: DashMap = DashMap::new(); + /// map.insert(1, ()); + /// map.insert(2, ()); + /// + /// let set = DashSet::from_dash_map(map); + /// assert!(set.contains(&1)); + /// assert!(set.contains(&2)); + /// ``` + pub fn from_dash_map(map: DashMap) -> Self { + Self { inner: map } + } +} + +impl From> for DashSet { + fn from(map: DashMap) -> Self { + Self { inner: map } + } +} + +impl From> for DashMap { + fn from(set: DashSet) -> Self { + set.inner + } } impl PartialEq for DashSet { diff --git a/src/util.rs b/src/util.rs index 6464aa6d..21c8ebb8 100644 --- a/src/util.rs +++ b/src/util.rs @@ -123,3 +123,79 @@ impl<'a, R: RawRwLockDowngrade> RwLockWriteGuardDetached<'a, R> { } } } + +/// A guard that owns a `Box<[MaybeUninit]>` and tracks how many elements +/// have been initialized. On drop it drops every initialized element; the +/// box itself handles deallocation. On success, [`Self::assume_init`] transmute\-s +/// the box into a `Box<[T]>` without dropping anything. +/// +/// This avoids an intermediate `Vec` allocation when constructing a +/// `Box<[T]>` element-by-element in a fallible loop. +pub(crate) struct InitSliceGuard { + slab: Box<[mem::MaybeUninit]>, + init: usize, +} + +impl InitSliceGuard { + /// Allocates a boxed slice of `len` uninitialized `T` elements. + /// + /// Returns `None` if the layout overflows or the allocation fails. + pub fn new(len: usize) -> Option { + let layout = core::alloc::Layout::array::>(len).ok()?; + let ptr = unsafe { std::alloc::alloc(layout) }; + if ptr.is_null() { + return None; + } + // SAFETY: `ptr` is non-null, correctly aligned, and sized for `len` + // elements of `MaybeUninit`. MaybeUninit has no invalid bitpatterns. + let slab: Box<[mem::MaybeUninit]> = unsafe { + Box::from_raw(ptr::slice_from_raw_parts_mut( + ptr as *mut mem::MaybeUninit, + len, + )) + }; + Some(Self { slab, init: 0 }) + } + + /// Returns a mutable pointer to the slot at index `i`. + /// + /// # Panics + /// + /// Panics if `i` is out of bounds. + pub fn get(&mut self, i: usize) -> *mut T { + self.slab[i].as_mut_ptr() + } + + /// Marks one more element as initialized. + pub fn mark_init(&mut self) { + self.init += 1; + } + + /// Consumes the guard and returns a `Box<[T]>`. + /// + /// # Safety + /// + /// All elements must have been initialized (i.e. `mark_init` called + /// exactly `slab.len()` times). No element may be left uninitialized. + pub unsafe fn assume_init(mut self) -> Box<[T]> { + // SAFETY: The caller guarantees all elements are initialized. + // Box<[MaybeUninit]> and Box<[T]> share the same fat-pointer layout, + // so casting the thin pointer is valid. We forget `self` to prevent + // the Drop impl from running (which would double-drop initialized elems). + let ptr = self.slab.as_mut_ptr().cast::(); + let len = self.slab.len(); + mem::forget(self); + unsafe { Box::from_raw(ptr::slice_from_raw_parts_mut(ptr, len)) } + } +} + +impl Drop for InitSliceGuard { + fn drop(&mut self) { + for i in 0..self.init { + // SAFETY: Elements 0..init have been written via get()/mark_init(). + unsafe { + ptr::drop_in_place(self.slab.as_mut_ptr().add(i).cast::()); + } + } + } +}