From 9ee7cea4115369207e4ad2a5d62b29948fdb738f Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Mon, 29 Jun 2020 16:26:32 -0400 Subject: [PATCH 1/2] Add HashMap variant with incremental resize This patch adds a variant of HashMap, `IncrHashMap`, that supports incremental resizing. It uses the same underlying hash table implementation as `HashMap` (i.e., `RawTable`), but where the standard implementation performs all-at-once resizing when the map must grow to accommodate new elements, this implementation instead spreads the resizing load across subsequent inserts. To do this, it keeps both a new, resized map, and the old, pre-resize map around after a resize. The new map starts out mostly empty, and read operations search in both tables. New inserts go into the new map, but they also move a few items from the old map to the new one. When the old map is empty, it is dropped, and all operations hit only the new map. This map implementation offers more stable insert performance than `HashMap`, but it does so at a cost: - Reads and removals of old or missing keys are slower for a while after a resize. - Inserts are slower than their `HashMap` counterparts for a while after a resize. - Memory is not reclaimed immediately after a resize, only after all the keys have been moved. - `IncrHashMap` is slightly larger than `HashMap`, to account for the bookkeeping needed to manage two maps during and after a resize. --- src/incremental.rs | 2062 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/raw/mod.rs | 7 +- 3 files changed, 2070 insertions(+), 1 deletion(-) create mode 100644 src/incremental.rs diff --git a/src/incremental.rs b/src/incremental.rs new file mode 100644 index 0000000000..857cf73fb1 --- /dev/null +++ b/src/incremental.rs @@ -0,0 +1,2062 @@ +use crate::map::{make_hash, DefaultHashBuilder}; +use crate::raw::{RawIntoIter, RawIter, RawTable}; +use core::borrow::Borrow; +use core::fmt::{self, Debug}; +use core::hash::{BuildHasher, Hash}; +use core::iter::{FromIterator, FusedIterator}; +use core::marker::PhantomData; +use core::mem; +use core::ops::Index; + +#[cfg(test)] +pub const R: usize = 4; +#[cfg(not(test))] +pub const R: usize = 8; + +#[derive(Clone)] +struct OldTable { + table: RawTable, + + // We cache an iterator over the old table's buckets so we don't need to do a linear search + // across buckets we know are empty each time we want to move more items. + items: RawIter, +} + +/// A hash map variant that spreads resize load across inserts. +/// +/// `IncrHashMap` uses the same underlying hash table implementation as [`HashMap`]. But where the +/// standard implementation performs all-at-once resizing when the map must grow to accommodate new +/// elements, this implementation instead spreads the resizing load across subsequent inserts. +/// +/// To do this, it keeps both a new, resized map, and the old, pre-resize map around after a +/// resize. The new map starts out mostly empty, and read operations search in both tables. New +/// inserts go into the new map, but they also move a few items from the old map to the new one. +/// When the old map is empty, it is dropped, and all operations hit only the new map. +/// +/// # Trade-offs +/// +/// This map implementation offers more stable insert performance than [`HashMap`], but it does so +/// at a cost: +/// +/// - Reads and removals of old or missing keys are slower for a while after a resize. +/// - Inserts are slower than their [`HashMap`] counterparts for a while after a resize. +/// - Memory is not reclaimed immediately after a resize, only after all the keys have been moved. +/// - [`IncrHashMap`] is slightly larger than [`HashMap`], to account for the bookkeeping needed +/// to manage two maps during and after a resize. +/// +/// # Examples +/// +/// ``` +/// use hashbrown::IncrHashMap; +/// +/// // Type inference lets us omit an explicit type signature (which +/// // would be `HashMap` in this example). +/// let mut book_reviews = IncrHashMap::new(); +/// +/// // Review some books. +/// book_reviews.insert( +/// "Adventures of Huckleberry Finn".to_string(), +/// "My favorite book.".to_string(), +/// ); +/// book_reviews.insert( +/// "Grimms' Fairy Tales".to_string(), +/// "Masterpiece.".to_string(), +/// ); +/// book_reviews.insert( +/// "Pride and Prejudice".to_string(), +/// "Very enjoyable.".to_string(), +/// ); +/// book_reviews.insert( +/// "The Adventures of Sherlock Holmes".to_string(), +/// "Eye lyked it alot.".to_string(), +/// ); +/// +/// // Check for a specific one. +/// // When collections store owned values (String), they can still be +/// // queried using references (&str). +/// if !book_reviews.contains_key("Les Misérables") { +/// println!("We've got {} reviews, but Les Misérables ain't one.", +/// book_reviews.len()); +/// } +/// +/// // oops, this review has a lot of spelling mistakes, let's delete it. +/// book_reviews.remove("The Adventures of Sherlock Holmes"); +/// +/// // Look up the values associated with some keys. +/// let to_find = ["Pride and Prejudice", "Alice's Adventure in Wonderland"]; +/// for &book in &to_find { +/// match book_reviews.get(book) { +/// Some(review) => println!("{}: {}", book, review), +/// None => println!("{} is unreviewed.", book) +/// } +/// } +/// +/// // Look up the value for a key (will panic if the key is not found). +/// println!("Review for Jane: {}", book_reviews["Pride and Prejudice"]); +/// +/// // Iterate over everything. +/// for (book, review) in &book_reviews { +/// println!("{}: \"{}\"", book, review); +/// } +/// ``` +/// +/// [`HashMap`]: struct.HashMap.html +pub struct IncrHashMap { + hash_builder: S, + table: RawTable<(K, V)>, + leftovers: Option>, +} + +impl Clone for IncrHashMap { + fn clone(&self) -> Self { + Self { + hash_builder: self.hash_builder.clone(), + table: self.table.clone(), + leftovers: self.leftovers.clone(), + } + } + + fn clone_from(&mut self, source: &Self) { + self.table.clone_from(&source.table); + self.leftovers.clone_from(&source.leftovers); + + // Update hash_builder only if we successfully cloned all elements. + self.hash_builder.clone_from(&source.hash_builder); + } +} + +#[cfg(feature = "ahash")] +impl IncrHashMap { + /// Creates an empty `IncrHashMap`. + /// + /// The hash map is initially created with a capacity of 0, so it will not allocate until it + /// is first inserted into. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// let mut map: IncrHashMap<&str, i32> = IncrHashMap::new(); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn new() -> Self { + Self::default() + } + + /// Creates an empty `IncrHashMap` with the specified capacity. + /// + /// The hash map will be able to hold at least `capacity` elements without + /// reallocating. If `capacity` is 0, the hash map will not allocate. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// let mut map: IncrHashMap<&str, i32> = IncrHashMap::with_capacity(10); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn with_capacity(capacity: usize) -> Self { + Self::with_capacity_and_hasher(capacity, DefaultHashBuilder::default()) + } +} + +impl IncrHashMap { + /// Creates an empty `IncrHashMap` which will use the given hash builder to hash keys. + /// + /// The created map has the default initial capacity. + /// + /// Warning: `hash_builder` is normally randomly generated, and + /// is designed to allow HashMaps to be resistant to attacks that + /// cause many collisions and very poor performance. Setting it + /// manually using this function can expose a DoS attack vector. + /// + /// The `hash_builder` passed should implement the [`BuildHasher`] trait for + /// the map to be useful, see its documentation for details. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// use hashbrown::hash_map::DefaultHashBuilder; + /// + /// let s = DefaultHashBuilder::default(); + /// let mut map = IncrHashMap::with_hasher(s); + /// map.insert(1, 2); + /// ``` + /// + /// [`BuildHasher`]: ../../std/hash/trait.BuildHasher.html + #[cfg_attr(feature = "inline-more", inline)] + pub fn with_hasher(hash_builder: S) -> Self { + Self { + hash_builder, + table: RawTable::new(), + leftovers: None, + } + } + + /// Creates an empty `IncrHashMap` with the specified capacity, using `hash_builder` to + /// hash the keys. + /// + /// The hash map will be able to hold at least `capacity` elements without + /// reallocating. If `capacity` is 0, the hash map will not allocate. + /// + /// Warning: `hash_builder` is normally randomly generated, and + /// is designed to allow HashMaps to be resistant to attacks that + /// cause many collisions and very poor performance. Setting it + /// manually using this function can expose a DoS attack vector. + /// + /// The `hash_builder` passed should implement the [`BuildHasher`] trait for + /// the map to be useful, see its documentation for details. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// use hashbrown::hash_map::DefaultHashBuilder; + /// + /// let s = DefaultHashBuilder::default(); + /// let mut map = IncrHashMap::with_capacity_and_hasher(10, s); + /// map.insert(1, 2); + /// ``` + /// + /// [`BuildHasher`]: ../../std/hash/trait.BuildHasher.html + #[cfg_attr(feature = "inline-more", inline)] + pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self { + Self { + hash_builder, + table: RawTable::with_capacity(capacity), + leftovers: None, + } + } + + /// Returns a reference to the map's [`BuildHasher`]. + /// + /// [`BuildHasher`]: https://doc.rust-lang.org/std/hash/trait.BuildHasher.html + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// use hashbrown::hash_map::DefaultHashBuilder; + /// + /// let hasher = DefaultHashBuilder::default(); + /// let map: IncrHashMap = IncrHashMap::with_hasher(hasher); + /// let hasher: &DefaultHashBuilder = map.hasher(); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn hasher(&self) -> &S { + &self.hash_builder + } + + /// Returns the number of elements the map can hold without reallocating. + /// + /// This number is a lower bound; the map might be able to hold + /// more, but is guaranteed to be able to hold at least this many. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// let map: IncrHashMap = IncrHashMap::with_capacity(100); + /// assert!(map.capacity() >= 100); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn capacity(&self) -> usize { + self.table.capacity() + } + + /// An iterator visiting all keys in arbitrary order. + /// The iterator element type is `&'a K`. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// map.insert("a", 1); + /// map.insert("b", 2); + /// map.insert("c", 3); + /// + /// for key in map.keys() { + /// println!("{}", key); + /// } + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn keys(&self) -> Keys<'_, K, V> { + Keys { inner: self.iter() } + } + + /// An iterator visiting all values in arbitrary order. + /// The iterator element type is `&'a V`. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// map.insert("a", 1); + /// map.insert("b", 2); + /// map.insert("c", 3); + /// + /// for val in map.values() { + /// println!("{}", val); + /// } + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn values(&self) -> Values<'_, K, V> { + Values { inner: self.iter() } + } + + /// An iterator visiting all values mutably in arbitrary order. + /// The iterator element type is `&'a mut V`. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// + /// map.insert("a", 1); + /// map.insert("b", 2); + /// map.insert("c", 3); + /// + /// for val in map.values_mut() { + /// *val = *val + 10; + /// } + /// + /// for val in map.values() { + /// println!("{}", val); + /// } + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> { + ValuesMut { + inner: self.iter_mut(), + } + } + + /// An iterator visiting all key-value pairs in arbitrary order. + /// The iterator element type is `(&'a K, &'a V)`. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// map.insert("a", 1); + /// map.insert("b", 2); + /// map.insert("c", 3); + /// + /// for (key, val) in map.iter() { + /// println!("key: {} val: {}", key, val); + /// } + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn iter(&self) -> Iter<'_, K, V> { + // Here we tie the lifetime of self to the iter. + unsafe { + Iter { + table: self.table.iter(), + leftovers: self.leftovers.as_ref().map(|lo| lo.items.clone()), + marker: PhantomData, + } + } + } + + /// An iterator visiting all key-value pairs in arbitrary order, + /// with mutable references to the values. + /// The iterator element type is `(&'a K, &'a mut V)`. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// map.insert("a", 1); + /// map.insert("b", 2); + /// map.insert("c", 3); + /// + /// // Update all values + /// for (_, val) in map.iter_mut() { + /// *val *= 2; + /// } + /// + /// for (key, val) in &map { + /// println!("key: {} val: {}", key, val); + /// } + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { + // Here we tie the lifetime of self to the iter. + unsafe { + IterMut { + table: self.table.iter(), + leftovers: self.leftovers.as_mut().map(|t| t.items.clone()), + marker: PhantomData, + } + } + } + + #[cfg(test)] + #[cfg_attr(feature = "inline-more", inline)] + fn raw_capacity(&self) -> (usize, Option) { + ( + self.table.buckets(), + self.leftovers.as_ref().map(|t| t.table.buckets()), + ) + } + + #[cfg(test)] + fn is_split(&self) -> bool { + self.leftovers.is_some() + } + + /// Returns the number of elements in the map. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut a = IncrHashMap::new(); + /// assert_eq!(a.len(), 0); + /// a.insert(1, "a"); + /// assert_eq!(a.len(), 1); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn len(&self) -> usize { + self.table.len() + self.leftovers.as_ref().map(|t| t.table.len()).unwrap_or(0) + } + + /// Returns `true` if the map contains no elements. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut a = IncrHashMap::new(); + /// assert!(a.is_empty()); + /// a.insert(1, "a"); + /// assert!(!a.is_empty()); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Clears the map, removing all key-value pairs. Keeps the allocated memory + /// for reuse. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut a = IncrHashMap::new(); + /// a.insert(1, "a"); + /// a.clear(); + /// assert!(a.is_empty()); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn clear(&mut self) { + let _ = self.leftovers.take(); + self.table.clear(); + } +} + +impl IncrHashMap +where + K: Eq + Hash, + S: BuildHasher, +{ + /// Returns a reference to the value corresponding to the key. + /// + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. + /// + /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// map.insert(1, "a"); + /// assert_eq!(map.get(&1), Some(&"a")); + /// assert_eq!(map.get(&2), None); + /// ``` + #[inline] + pub fn get(&self, k: &Q) -> Option<&V> + where + K: Borrow, + Q: Hash + Eq, + { + self.get_key_value(k).map(|(_, v)| v) + } + + /// Returns the key-value pair corresponding to the supplied key. + /// + /// The supplied key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. + /// + /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// map.insert(1, "a"); + /// assert_eq!(map.get_key_value(&1), Some((&1, &"a"))); + /// assert_eq!(map.get_key_value(&2), None); + /// ``` + #[inline] + pub fn get_key_value(&self, k: &Q) -> Option<(&K, &V)> + where + K: Borrow, + Q: Hash + Eq, + { + let hash = make_hash(&self.hash_builder, k); + let find = |t: &RawTable<(K, V)>| { + t.find(hash, |x| k.eq(x.0.borrow())).map(|item| unsafe { + let &(ref key, ref value) = item.as_ref(); + (key, value) + }) + }; + + if let Some(e) = find(&self.table) { + return Some(e); + } + + if let Some(OldTable { ref table, .. }) = self.leftovers { + find(table) + } else { + None + } + } + + /// Returns `true` if the map contains a value for the specified key. + /// + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. + /// + /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// map.insert(1, "a"); + /// assert_eq!(map.contains_key(&1), true); + /// assert_eq!(map.contains_key(&2), false); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn contains_key(&self, k: &Q) -> bool + where + K: Borrow, + Q: Hash + Eq, + { + self.get(k).is_some() + } + + /// Returns a mutable reference to the value corresponding to the key. + /// + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. + /// + /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// map.insert(1, "a"); + /// if let Some(x) = map.get_mut(&1) { + /// *x = "b"; + /// } + /// assert_eq!(map[&1], "b"); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn get_mut(&mut self, k: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Hash + Eq, + { + let hash = make_hash(&self.hash_builder, k); + let find = |t: &mut RawTable<(K, V)>| { + t.find(hash, |x| k.eq(x.0.borrow())) + .map(|item| unsafe { &mut item.as_mut().1 }) + }; + + let e = find(&mut self.table); + if e.is_some() { + return e; + } + + if let Some(OldTable { ref mut table, .. }) = self.leftovers { + find(table) + } else { + None + } + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not have this key present, [`None`] is returned. + /// + /// If the map did have this key present, the value is updated, and the old + /// value is returned. The key is not updated, though; this matters for + /// types that can be `==` without being identical. See the [module-level + /// documentation] for more. + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// [module-level documentation]: index.html#insert-and-complex-keys + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// assert_eq!(map.insert(37, "a"), None); + /// assert_eq!(map.is_empty(), false); + /// + /// map.insert(37, "b"); + /// assert_eq!(map.insert(37, "c"), Some("b")); + /// assert_eq!(map[&37], "c"); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn insert(&mut self, k: K, v: V) -> Option { + let hash = make_hash(&self.hash_builder, &k); + + unsafe { + if let Some(item) = self.table.find(hash, |x| k.eq(&x.0)) { + // The item is in the main table, so just replace it. + // NOTE: we do not carry here to keep the happy path fast. + Some(mem::replace(&mut item.as_mut().1, v)) + } else if let Some(ref mut lo) = self.leftovers { + if let Some(item) = lo.table.find(hash, |x| k.eq(&x.0)) { + // The item is in leftover table, and hasn't been moved yet, so replace it. + let v = Some(mem::replace(&mut item.as_mut().1, v)); + // Also carry over items too while we're at it. + self.carry(); + v + } else { + // The item is not present at all, so insert it into the main table. + let hash_builder = &self.hash_builder; + self.table + .insert(hash, (k, v), |x| make_hash(hash_builder, &x.0)); + // Also carry some items over. + self.carry(); + None + } + } else { + if self.table.capacity() == self.table.len() { + // Even though this _may_ succeed without growing due to tombstones, handling + // that case is convoluted, so we just assume this would grow the map. + // + // We need to grow the table by at least a factor of (R + 1)/R to ensure that + // the new table won't _also_ grow while we're still moving items from the old + // one. We'll favor factor-of-two growing if that's sufficient though. + let need_cap = ((R + 1) / R) * self.table.len(); + let next_table_cap = usize::max(need_cap, self.table.full_capacity() + 1); + + let mut new_table = RawTable::with_capacity(next_table_cap); + let hash_builder = &self.hash_builder; + new_table.insert(hash, (k, v), |x| make_hash(hash_builder, &x.0)); + let old_table = mem::replace(&mut self.table, new_table); + let old_table_items = old_table.iter(); + self.leftovers = Some(OldTable { + table: old_table, + items: old_table_items, + }); + self.carry(); + } else { + let hash_builder = &self.hash_builder; + self.table + .insert(hash, (k, v), |x| make_hash(hash_builder, &x.0)); + } + None + } + } + } + + #[cold] + #[inline(never)] + fn carry(&mut self) { + if let Some(ref mut lo) = self.leftovers { + let hash_builder = &self.hash_builder; + for _ in 0..R { + // It is safe to continue to access this iterator because: + // - we have not de-allocated the table it points into + // - we have not grown or shrunk the table it points into + // + // NOTE: Calling next here could be expensive, as the iter needs to search for the + // next non-empty bucket. as the map grows in size, that search time will increase + // linearly. + if let Some(e) = lo.items.next() { + // We need to remove the item in this bucket from the old map + // to the resized map, without shrinking the old map. + let (k, v) = unsafe { + lo.table.erase_no_drop(&e); + e.read() + }; + let hash = make_hash(&self.hash_builder, &k); + self.table + .insert(hash, (k, v), |x| make_hash(hash_builder, &x.0)); + } else { + // The resize is finally fully complete. + let _ = self.leftovers.take(); + break; + } + } + } + } + + /// Removes a key from the map, returning the value at the key if the key + /// was previously in the map. + /// + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. + /// + /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// map.insert(1, "a"); + /// assert_eq!(map.remove(&1), Some("a")); + /// assert_eq!(map.remove(&1), None); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn remove(&mut self, k: &Q) -> Option + where + K: Borrow, + Q: Hash + Eq, + { + self.remove_entry(k).map(|(_, v)| v) + } + + /// Removes a key from the map, returning the stored key and value if the + /// key was previously in the map. + /// + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. + /// + /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// map.insert(1, "a"); + /// assert_eq!(map.remove_entry(&1), Some((1, "a"))); + /// assert_eq!(map.remove(&1), None); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn remove_entry(&mut self, k: &Q) -> Option<(K, V)> + where + K: Borrow, + Q: Hash + Eq, + { + unsafe { + let hash = make_hash(&self.hash_builder, &k); + if let Some(item) = self.table.find(hash, |x| k.eq(x.0.borrow())) { + self.table.erase_no_drop(&item); + Some(item.read()) + } else if let Some(ref mut lo) = self.leftovers { + if let Some(item) = lo.table.find(hash, |x| k.eq(x.0.borrow())) { + lo.table.erase_no_drop(&item); + + // By changing the state of the table, we have invalidated the table iterator + // we keep for what elements are left to move. So, we re-compute it. + // + // TODO: We should be able to "fix up" the iterator rather than replace it, + // which would save us from iterating over the prefix of empty buckets we've + // left in our wake from the moves so far. + lo.items = lo.table.iter(); + Some(item.read()) + } else { + None + } + } else { + None + } + } + } +} + +impl PartialEq for IncrHashMap +where + K: Eq + Hash, + V: PartialEq, + S: BuildHasher, +{ + fn eq(&self, other: &Self) -> bool { + if self.len() != other.len() { + return false; + } + + self.iter() + .all(|(key, value)| other.get(key).map_or(false, |v| *value == *v)) + } +} + +impl Eq for IncrHashMap +where + K: Eq + Hash, + V: Eq, + S: BuildHasher, +{ +} + +impl Debug for IncrHashMap +where + K: Debug, + V: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map().entries(self.iter()).finish() + } +} + +impl Default for IncrHashMap +where + S: Default, +{ + /// Creates an empty `IncrHashMap`, with the `Default` value for the hasher. + #[cfg_attr(feature = "inline-more", inline)] + fn default() -> Self { + Self::with_hasher(Default::default()) + } +} + +impl Index<&Q> for IncrHashMap +where + K: Eq + Hash + Borrow, + Q: Eq + Hash, + S: BuildHasher, +{ + type Output = V; + + /// Returns a reference to the value corresponding to the supplied key. + /// + /// # Panics + /// + /// Panics if the key is not present in the `HashMap`. + #[cfg_attr(feature = "inline-more", inline)] + fn index(&self, key: &Q) -> &V { + self.get(key).expect("no entry found for key") + } +} + +/// An iterator over the entries of a `IncrHashMap`. +/// +/// This `struct` is created by the [`iter`] method on [`IncrHashMap`]. See its +/// documentation for more. +/// +/// [`iter`]: struct.IncrHashMap.html#method.iter +/// [`IncrHashMap`]: struct.IncrHashMap.html +pub struct Iter<'a, K, V> { + table: RawIter<(K, V)>, + leftovers: Option>, + marker: PhantomData<(&'a K, &'a V)>, +} + +// FIXME(#26925) Remove in favor of `#[derive(Clone)]` +impl Clone for Iter<'_, K, V> { + #[cfg_attr(feature = "inline-more", inline)] + fn clone(&self) -> Self { + Iter { + table: self.table.clone(), + leftovers: self.leftovers.clone(), + marker: PhantomData, + } + } +} + +impl fmt::Debug for Iter<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// A mutable iterator over the entries of a `IncrHashMap`. +/// +/// This `struct` is created by the [`iter_mut`] method on [`IncrHashMap`]. See its +/// documentation for more. +/// +/// [`iter_mut`]: struct.IncrHashMap.html#method.iter_mut +/// [`IncrHashMap`]: struct.IncrHashMap.html +pub struct IterMut<'a, K, V> { + table: RawIter<(K, V)>, + leftovers: Option>, + // To ensure invariance with respect to V + marker: PhantomData<(&'a K, &'a mut V)>, +} + +// We override the default Send impl which has K: Sync instead of K: Send. Both +// are correct, but this one is more general since it allows keys which +// implement Send but not Sync. +unsafe impl Send for IterMut<'_, K, V> {} + +impl IterMut<'_, K, V> { + /// Returns a iterator of references over the remaining items. + #[cfg_attr(feature = "inline-more", inline)] + pub(super) fn iter(&self) -> Iter<'_, K, V> { + Iter { + table: self.table.clone(), + leftovers: self.leftovers.clone(), + marker: PhantomData, + } + } +} + +/// An owning iterator over the entries of a `IncrHashMap`. +/// +/// This `struct` is created by the [`into_iter`] method on [`IncrHashMap`] +/// (provided by the `IntoIterator` trait). See its documentation for more. +/// +/// [`into_iter`]: struct.IncrHashMap.html#method.into_iter +/// [`IncrHashMap`]: struct.IncrHashMap.html +pub struct IntoIter { + table: RawIntoIter<(K, V)>, + leftovers: Option>, +} + +impl IntoIter { + /// Returns a iterator of references over the remaining items. + #[cfg_attr(feature = "inline-more", inline)] + pub(super) fn iter(&self) -> Iter<'_, K, V> { + Iter { + table: self.table.iter(), + leftovers: self.leftovers.as_ref().map(|i| i.iter()), + marker: PhantomData, + } + } +} + +/// An iterator over the keys of a `IncrHashMap`. +/// +/// This `struct` is created by the [`keys`] method on [`IncrHashMap`]. See its +/// documentation for more. +/// +/// [`keys`]: struct.IncrHashMap.html#method.keys +/// [`IncrHashMap`]: struct.IncrHashMap.html +pub struct Keys<'a, K, V> { + inner: Iter<'a, K, V>, +} + +// FIXME(#26925) Remove in favor of `#[derive(Clone)]` +impl Clone for Keys<'_, K, V> { + #[cfg_attr(feature = "inline-more", inline)] + fn clone(&self) -> Self { + Keys { + inner: self.inner.clone(), + } + } +} + +impl fmt::Debug for Keys<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// An iterator over the values of a `IncrHashMap`. +/// +/// This `struct` is created by the [`values`] method on [`IncrHashMap`]. See its +/// documentation for more. +/// +/// [`values`]: struct.IncrHashMap.html#method.values +/// [`IncrHashMap`]: struct.IncrHashMap.html +pub struct Values<'a, K, V> { + inner: Iter<'a, K, V>, +} + +// FIXME(#26925) Remove in favor of `#[derive(Clone)]` +impl Clone for Values<'_, K, V> { + #[cfg_attr(feature = "inline-more", inline)] + fn clone(&self) -> Self { + Values { + inner: self.inner.clone(), + } + } +} + +impl fmt::Debug for Values<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// A mutable iterator over the values of a `IncrHashMap`. +/// +/// This `struct` is created by the [`values_mut`] method on [`IncrHashMap`]. See its +/// documentation for more. +/// +/// [`values_mut`]: struct.IncrHashMap.html#method.values_mut +/// [`IncrHashMap`]: struct.IncrHashMap.html +pub struct ValuesMut<'a, K, V> { + inner: IterMut<'a, K, V>, +} + +impl<'a, K, V, S> IntoIterator for &'a IncrHashMap { + type Item = (&'a K, &'a V); + type IntoIter = Iter<'a, K, V>; + + #[cfg_attr(feature = "inline-more", inline)] + fn into_iter(self) -> Iter<'a, K, V> { + self.iter() + } +} + +impl<'a, K, V, S> IntoIterator for &'a mut IncrHashMap { + type Item = (&'a K, &'a mut V); + type IntoIter = IterMut<'a, K, V>; + + #[cfg_attr(feature = "inline-more", inline)] + fn into_iter(self) -> IterMut<'a, K, V> { + self.iter_mut() + } +} + +impl IntoIterator for IncrHashMap { + type Item = (K, V); + type IntoIter = IntoIter; + + /// Creates a consuming iterator, that is, one that moves each key-value + /// pair out of the map in arbitrary order. The map cannot be used after + /// calling this. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::IncrHashMap; + /// + /// let mut map = IncrHashMap::new(); + /// map.insert("a", 1); + /// map.insert("b", 2); + /// map.insert("c", 3); + /// + /// // Not possible with .iter() + /// let vec: Vec<(&str, i32)> = map.into_iter().collect(); + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + fn into_iter(self) -> IntoIter { + IntoIter { + table: self.table.into_iter(), + leftovers: self.leftovers.map(|t| { + // TODO: make this re-use knowledge of progress from t.items + t.table.into_iter() + }), + } + } +} + +impl<'a, K, V> Iterator for Iter<'a, K, V> { + type Item = (&'a K, &'a V); + + #[cfg_attr(feature = "inline-more", inline)] + fn next(&mut self) -> Option<(&'a K, &'a V)> { + let leftovers = &mut self.leftovers; + self.table + .next() + .or_else(|| leftovers.as_mut()?.next()) + .map(|x| unsafe { + let r = x.as_ref(); + (&r.0, &r.1) + }) + } + #[cfg_attr(feature = "inline-more", inline)] + fn size_hint(&self) -> (usize, Option) { + let (mut lo, mut hi) = self.table.size_hint(); + if let Some(ref left) = self.leftovers { + let (lo2, hi2) = left.size_hint(); + lo += lo2; + if let (Some(ref mut hi), Some(hi2)) = (&mut hi, hi2) { + *hi += hi2; + } + } + (lo, hi) + } +} +impl ExactSizeIterator for Iter<'_, K, V> { + #[cfg_attr(feature = "inline-more", inline)] + fn len(&self) -> usize { + self.table.len() + self.leftovers.as_ref().map(|lo| lo.len()).unwrap_or(0) + } +} + +impl FusedIterator for Iter<'_, K, V> {} + +impl<'a, K, V> Iterator for IterMut<'a, K, V> { + type Item = (&'a K, &'a mut V); + + #[cfg_attr(feature = "inline-more", inline)] + fn next(&mut self) -> Option<(&'a K, &'a mut V)> { + let leftovers = &mut self.leftovers; + self.table + .next() + .or_else(|| leftovers.as_mut()?.next()) + .map(|x| unsafe { + let r = x.as_mut(); + (&r.0, &mut r.1) + }) + } + #[cfg_attr(feature = "inline-more", inline)] + fn size_hint(&self) -> (usize, Option) { + let (mut lo, mut hi) = self.table.size_hint(); + if let Some(ref left) = self.leftovers { + let (lo2, hi2) = left.size_hint(); + lo += lo2; + if let (Some(ref mut hi), Some(hi2)) = (&mut hi, hi2) { + *hi += hi2; + } + } + (lo, hi) + } +} +impl ExactSizeIterator for IterMut<'_, K, V> { + #[cfg_attr(feature = "inline-more", inline)] + fn len(&self) -> usize { + self.table.len() + self.leftovers.as_ref().map(|lo| lo.len()).unwrap_or(0) + } +} +impl FusedIterator for IterMut<'_, K, V> {} + +impl fmt::Debug for IterMut<'_, K, V> +where + K: fmt::Debug, + V: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.iter()).finish() + } +} + +impl Iterator for IntoIter { + type Item = (K, V); + + #[cfg_attr(feature = "inline-more", inline)] + fn next(&mut self) -> Option<(K, V)> { + let leftovers = &mut self.leftovers; + self.table.next().or_else(|| leftovers.as_mut()?.next()) + } + #[cfg_attr(feature = "inline-more", inline)] + fn size_hint(&self) -> (usize, Option) { + let (mut lo, mut hi) = self.table.size_hint(); + if let Some(ref left) = self.leftovers { + let (lo2, hi2) = left.size_hint(); + lo += lo2; + if let (Some(ref mut hi), Some(hi2)) = (&mut hi, hi2) { + *hi += hi2; + } + } + (lo, hi) + } +} +impl ExactSizeIterator for IntoIter { + #[cfg_attr(feature = "inline-more", inline)] + fn len(&self) -> usize { + self.table.len() + self.leftovers.as_ref().map(|lo| lo.len()).unwrap_or(0) + } +} +impl FusedIterator for IntoIter {} + +impl fmt::Debug for IntoIter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.iter()).finish() + } +} + +impl<'a, K, V> Iterator for Keys<'a, K, V> { + type Item = &'a K; + + #[cfg_attr(feature = "inline-more", inline)] + fn next(&mut self) -> Option<&'a K> { + self.inner.next().map(|(k, _)| k) + } + #[cfg_attr(feature = "inline-more", inline)] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} +impl ExactSizeIterator for Keys<'_, K, V> { + #[cfg_attr(feature = "inline-more", inline)] + fn len(&self) -> usize { + self.inner.len() + } +} +impl FusedIterator for Keys<'_, K, V> {} + +impl<'a, K, V> Iterator for Values<'a, K, V> { + type Item = &'a V; + + #[cfg_attr(feature = "inline-more", inline)] + fn next(&mut self) -> Option<&'a V> { + self.inner.next().map(|(_, v)| v) + } + #[cfg_attr(feature = "inline-more", inline)] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} +impl ExactSizeIterator for Values<'_, K, V> { + #[cfg_attr(feature = "inline-more", inline)] + fn len(&self) -> usize { + self.inner.len() + } +} +impl FusedIterator for Values<'_, K, V> {} + +impl<'a, K, V> Iterator for ValuesMut<'a, K, V> { + type Item = &'a mut V; + + #[cfg_attr(feature = "inline-more", inline)] + fn next(&mut self) -> Option<&'a mut V> { + self.inner.next().map(|(_, v)| v) + } + #[cfg_attr(feature = "inline-more", inline)] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} +impl ExactSizeIterator for ValuesMut<'_, K, V> { + #[cfg_attr(feature = "inline-more", inline)] + fn len(&self) -> usize { + self.inner.len() + } +} +impl FusedIterator for ValuesMut<'_, K, V> {} + +impl fmt::Debug for ValuesMut<'_, K, V> +where + K: fmt::Debug, + V: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.inner.iter()).finish() + } +} + +impl FromIterator<(K, V)> for IncrHashMap +where + K: Eq + Hash, + S: BuildHasher + Default, +{ + #[cfg_attr(feature = "inline-more", inline)] + fn from_iter>(iter: T) -> Self { + let iter = iter.into_iter(); + let mut map = Self::with_capacity_and_hasher(iter.size_hint().0, S::default()); + iter.for_each(|(k, v)| { + map.insert(k, v); + }); + map + } +} + +#[allow(dead_code)] +fn assert_covariance() { + fn map_key<'new>(v: IncrHashMap<&'static str, u8>) -> IncrHashMap<&'new str, u8> { + v + } + fn map_val<'new>(v: IncrHashMap) -> IncrHashMap { + v + } + fn iter_key<'a, 'new>(v: Iter<'a, &'static str, u8>) -> Iter<'a, &'new str, u8> { + v + } + fn iter_val<'a, 'new>(v: Iter<'a, u8, &'static str>) -> Iter<'a, u8, &'new str> { + v + } + fn into_iter_key<'new>(v: IntoIter<&'static str, u8>) -> IntoIter<&'new str, u8> { + v + } + fn into_iter_val<'new>(v: IntoIter) -> IntoIter { + v + } + fn keys_key<'a, 'new>(v: Keys<'a, &'static str, u8>) -> Keys<'a, &'new str, u8> { + v + } + fn keys_val<'a, 'new>(v: Keys<'a, u8, &'static str>) -> Keys<'a, u8, &'new str> { + v + } + fn values_key<'a, 'new>(v: Values<'a, &'static str, u8>) -> Values<'a, &'new str, u8> { + v + } + fn values_val<'a, 'new>(v: Values<'a, u8, &'static str>) -> Values<'a, u8, &'new str> { + v + } +} + +#[cfg(test)] +mod test_map { + use super::DefaultHashBuilder; + use super::IncrHashMap; + use std::cell::RefCell; + use std::usize; + use std::vec::Vec; + + #[test] + fn test_zero_capacities() { + type HM = IncrHashMap; + + let m = HM::new(); + assert_eq!(m.capacity(), 0); + + let m = HM::default(); + assert_eq!(m.capacity(), 0); + + let m = HM::with_hasher(DefaultHashBuilder::default()); + assert_eq!(m.capacity(), 0); + + let m = HM::with_capacity(0); + assert_eq!(m.capacity(), 0); + + let m = HM::with_capacity_and_hasher(0, DefaultHashBuilder::default()); + assert_eq!(m.capacity(), 0); + } + + #[test] + fn test_create_capacity_zero() { + let mut m = IncrHashMap::with_capacity(0); + + assert!(m.insert(1, 1).is_none()); + + assert!(m.contains_key(&1)); + assert!(!m.contains_key(&0)); + } + + #[test] + fn test_insert() { + let mut m = IncrHashMap::new(); + assert_eq!(m.len(), 0); + assert!(m.insert(1, 2).is_none()); + assert_eq!(m.len(), 1); + assert!(m.insert(2, 4).is_none()); + assert_eq!(m.len(), 2); + assert_eq!(*m.get(&1).unwrap(), 2); + assert_eq!(*m.get(&2).unwrap(), 4); + } + + #[test] + fn test_split_insert() { + // the code below assumes that R is 4 + assert_eq!(super::R, 4); + + let mut m = IncrHashMap::new(); + assert_eq!(m.capacity(), 0); + // three inserts won't split + for i in 1..=3 { + m.insert(i, i); + assert_eq!(m.get(&i), Some(&i)); + assert_eq!(m.capacity(), 3); + } + // fourth insert will split and migrate all elements + assert!(!m.is_split()); + m.insert(4, 4); + // capacity should now be doubled + assert_eq!(m.capacity(), 7); + // and there should be no leftovers + assert!(!m.is_split()); + assert_eq!(m.table.len(), 4); + for i in 1..=4 { + assert_eq!(m.get(&i), Some(&i)); + } + + // move to next split point + for i in 5..=7 { + m.insert(i, i); + assert_eq!(m.get(&i), Some(&i)); + assert_eq!(m.capacity(), 7); + } + + // next insert will split, and move some, but not all (since R < old.len()) + m.insert(8, 8); + // capacity should now be doubled + assert_eq!(m.capacity(), 14); + // and there should be leftovers + assert!(m.is_split()); + assert_eq!(m.table.len(), 1 + super::R); + for i in 1..=8 { + assert_eq!(m.get(&i), Some(&i)); + } + // check that the iterators do the right thing when split: + assert_eq!(m.iter().count(), 8); + for i in 1..=8 { + assert!(m.iter().any(|(&e, _)| e == i)); + } + assert_eq!(m.iter_mut().count(), 8); + for i in 1..=8 { + assert!(m.iter_mut().any(|(&e, _)| e == i)); + } + + // if we do another insert, it will move the rest of the items from the old map + m.insert(9, 9); + assert!(!m.is_split()); + assert_eq!(m.table.len(), 9); + // it should not have changed capacity + assert_eq!(m.capacity(), 14); + for i in 1..=9 { + assert_eq!(m.get(&i), Some(&i)); + } + } + + #[test] + fn test_clone() { + let mut m = IncrHashMap::new(); + for i in 1..=8 { + assert_eq!(m.len(), i - 1); + assert!(m.insert(i, i + 1).is_none()); + } + assert_eq!(m.len(), 8); + let m2 = m.clone(); + for i in 1..=8 { + assert_eq!(m2.get(&i).copied(), Some(i + 1)); + } + assert_eq!(m2.len(), 8); + } + + #[test] + fn test_clone_from() { + let mut m = IncrHashMap::new(); + let mut m2 = IncrHashMap::new(); + for i in 1..=8 { + assert_eq!(m.len(), i - 1); + assert!(m.insert(i, i + 1).is_none()); + } + assert_eq!(m.len(), 8); + m2.clone_from(&m); + for i in 1..=8 { + assert_eq!(m2.get(&i).copied(), Some(i + 1)); + } + assert_eq!(m2.len(), 8); + } + + thread_local! { static DROP_VECTOR: RefCell> = RefCell::new(Vec::new()) } + + #[derive(Hash, PartialEq, Eq)] + struct Droppable { + k: usize, + } + + impl Droppable { + fn new(k: usize) -> Droppable { + DROP_VECTOR.with(|slot| { + slot.borrow_mut()[k] += 1; + }); + + Droppable { k } + } + } + + impl Drop for Droppable { + fn drop(&mut self) { + DROP_VECTOR.with(|slot| { + slot.borrow_mut()[self.k] -= 1; + }); + } + } + + impl Clone for Droppable { + fn clone(&self) -> Self { + Droppable::new(self.k) + } + } + + #[test] + fn test_drops() { + DROP_VECTOR.with(|slot| { + *slot.borrow_mut() = vec![0; 200]; + }); + + { + let mut m = IncrHashMap::new(); + + DROP_VECTOR.with(|v| { + for i in 0..200 { + assert_eq!(v.borrow()[i], 0); + } + }); + + for i in 0..100 { + let d1 = Droppable::new(i); + let d2 = Droppable::new(i + 100); + m.insert(d1, d2); + } + + DROP_VECTOR.with(|v| { + for i in 0..200 { + assert_eq!(v.borrow()[i], 1); + } + }); + + for i in 0..50 { + let k = Droppable::new(i); + let v = m.remove(&k); + + assert!(v.is_some()); + + DROP_VECTOR.with(|v| { + assert_eq!(v.borrow()[i], 1); + assert_eq!(v.borrow()[i + 100], 1); + }); + } + + DROP_VECTOR.with(|v| { + for i in 0..50 { + assert_eq!(v.borrow()[i], 0); + assert_eq!(v.borrow()[i + 100], 0); + } + + for i in 50..100 { + assert_eq!(v.borrow()[i], 1); + assert_eq!(v.borrow()[i + 100], 1); + } + }); + } + + DROP_VECTOR.with(|v| { + for i in 0..200 { + assert_eq!(v.borrow()[i], 0); + } + }); + } + + #[test] + fn test_into_iter_drops() { + DROP_VECTOR.with(|v| { + *v.borrow_mut() = vec![0; 200]; + }); + + let hm = { + let mut hm = IncrHashMap::new(); + + DROP_VECTOR.with(|v| { + for i in 0..200 { + assert_eq!(v.borrow()[i], 0); + } + }); + + for i in 0..100 { + let d1 = Droppable::new(i); + let d2 = Droppable::new(i + 100); + hm.insert(d1, d2); + } + + DROP_VECTOR.with(|v| { + for i in 0..200 { + assert_eq!(v.borrow()[i], 1); + } + }); + + hm + }; + + // By the way, ensure that cloning doesn't screw up the dropping. + drop(hm.clone()); + + { + let mut half = hm.into_iter().take(50); + + DROP_VECTOR.with(|v| { + for i in 0..200 { + assert_eq!(v.borrow()[i], 1); + } + }); + + for _ in half.by_ref() {} + + DROP_VECTOR.with(|v| { + let nk = (0..100).filter(|&i| v.borrow()[i] == 1).count(); + + let nv = (0..100).filter(|&i| v.borrow()[i + 100] == 1).count(); + + assert_eq!(nk, 50); + assert_eq!(nv, 50); + }); + }; + + DROP_VECTOR.with(|v| { + for i in 0..200 { + assert_eq!(v.borrow()[i], 0); + } + }); + } + + #[test] + fn test_empty_remove() { + let mut m: IncrHashMap = IncrHashMap::new(); + assert_eq!(m.remove(&0), None); + } + + #[test] + fn test_empty_iter() { + let mut m: IncrHashMap = IncrHashMap::new(); + assert_eq!(m.keys().next(), None); + assert_eq!(m.values().next(), None); + assert_eq!(m.values_mut().next(), None); + assert_eq!(m.iter().next(), None); + assert_eq!(m.iter_mut().next(), None); + assert_eq!(m.len(), 0); + assert!(m.is_empty()); + assert_eq!(m.into_iter().next(), None); + } + + #[test] + #[cfg_attr(miri, ignore)] // FIXME: takes too long + #[ignore] + fn test_lots_of_insertions() { + let mut m = IncrHashMap::new(); + + // Try this a few times to make sure we never screw up the hashmap's + // internal state. + for _ in 0..10 { + assert!(m.is_empty()); + + for i in 1..1001 { + assert!(m.insert(i, i).is_none()); + + for j in 1..=i { + let r = m.get(&j); + assert_eq!(r, Some(&j)); + } + + for j in i + 1..1001 { + let r = m.get(&j); + assert_eq!(r, None); + } + } + + for i in 1001..2001 { + assert!(!m.contains_key(&i)); + } + + // remove forwards + for i in 1..1001 { + assert!(m.remove(&i).is_some()); + + for j in 1..=i { + assert!(!m.contains_key(&j)); + } + + for j in i + 1..1001 { + assert!(m.contains_key(&j)); + } + } + + for i in 1..1001 { + assert!(!m.contains_key(&i)); + } + + for i in 1..1001 { + assert!(m.insert(i, i).is_none()); + } + + // remove backwards + for i in (1..1001).rev() { + assert!(m.remove(&i).is_some()); + + for j in i..1001 { + assert!(!m.contains_key(&j)); + } + + for j in 1..i { + assert!(m.contains_key(&j)); + } + } + } + } + + #[test] + fn test_find_mut() { + let mut m = IncrHashMap::new(); + // ensure that the map splits + for i in 1..=6 { + m.insert(1000 + i, i); + } + assert!(!m.is_split()); + assert!(m.insert(1, 12).is_none()); + assert!(!m.is_split()); + assert!(m.insert(2, 8).is_none()); + assert!(m.is_split()); + assert!(m.insert(5, 14).is_none()); + let new = 100; + match m.get_mut(&5) { + None => panic!(), + Some(x) => *x = new, + } + assert_eq!(m.get(&5), Some(&new)); + } + + #[test] + fn test_insert_overwrite() { + let mut m = IncrHashMap::new(); + // ensure that the map splits + for i in 1..=7 { + m.insert(1000 + i, i); + } + assert!(!m.is_split()); + assert!(m.insert(1, 2).is_none()); + assert!(m.is_split()); + assert_eq!(*m.get(&1).unwrap(), 2); + assert!(!m.insert(1, 3).is_none()); + assert_eq!(*m.get(&1).unwrap(), 3); + } + + #[test] + fn test_insert_conflicts() { + let mut m = IncrHashMap::with_capacity(4); + // ensure that the map splits + for i in 1..=7 { + m.insert(1000 + i, i); + } + assert!(!m.is_split()); + assert!(m.insert(1, 2).is_none()); + assert!(m.is_split()); + assert!(m.insert(5, 3).is_none()); + assert!(m.insert(9, 4).is_none()); + assert_eq!(*m.get(&9).unwrap(), 4); + assert_eq!(*m.get(&5).unwrap(), 3); + assert_eq!(*m.get(&1).unwrap(), 2); + } + + #[test] + fn test_conflict_remove() { + let mut m = IncrHashMap::with_capacity(4); + // ensure that the map splits + for i in 1..=5 { + m.insert(1000 + i, i); + } + assert!(!m.is_split()); + assert!(m.insert(1, 2).is_none()); + assert_eq!(*m.get(&1).unwrap(), 2); + assert!(m.insert(5, 3).is_none()); + assert!(!m.is_split()); + assert_eq!(*m.get(&1).unwrap(), 2); + assert_eq!(*m.get(&5).unwrap(), 3); + assert!(m.insert(9, 4).is_none()); + assert!(m.is_split()); + assert_eq!(*m.get(&1).unwrap(), 2); + assert_eq!(*m.get(&5).unwrap(), 3); + assert_eq!(*m.get(&9).unwrap(), 4); + assert!(m.remove(&1).is_some()); + assert_eq!(*m.get(&9).unwrap(), 4); + assert_eq!(*m.get(&5).unwrap(), 3); + } + + #[test] + fn test_is_empty() { + let mut m = IncrHashMap::with_capacity(4); + assert!(m.insert(1, 2).is_none()); + assert!(!m.is_empty()); + assert!(m.remove(&1).is_some()); + assert!(m.is_empty()); + } + + #[test] + fn test_remove() { + let mut m = IncrHashMap::new(); + m.insert(1, 2); + assert_eq!(m.remove(&1), Some(2)); + assert_eq!(m.remove(&1), None); + } + + #[test] + fn test_remove_entry() { + let mut m = IncrHashMap::new(); + m.insert(1, 2); + assert_eq!(m.remove_entry(&1), Some((1, 2))); + assert_eq!(m.remove(&1), None); + } + + #[test] + fn test_iterate() { + let mut m = IncrHashMap::with_capacity(4); + for i in 0..32 { + assert!(m.insert(i, i * 2).is_none()); + } + assert!(m.is_split()); + assert_eq!(m.len(), 32); + + let mut observed: u32 = 0; + + for (k, v) in &m { + assert_eq!(*v, *k * 2); + observed |= 1 << *k; + } + assert_eq!(observed, 0xFFFF_FFFF); + } + + #[test] + fn test_keys() { + let mut map = IncrHashMap::new(); + for (k, v) in (1..).zip('a'..).take(8) { + map.insert(k, v); + } + assert!(map.is_split()); + let keys: Vec<_> = map.keys().cloned().collect(); + assert_eq!(keys.len(), 8); + for i in 1..=8 { + assert!(keys.contains(&i)); + } + } + + #[test] + fn test_values() { + let mut map = IncrHashMap::new(); + for (k, v) in (1..).zip('a'..).take(8) { + map.insert(k, v); + } + assert!(map.is_split()); + let values: Vec<_> = map.values().cloned().collect(); + assert_eq!(values.len(), 8); + for c in 'a'..='g' { + assert!(values.contains(&c)); + } + } + + #[test] + fn test_values_mut() { + let mut map = IncrHashMap::new(); + for v in (1..).take(8) { + map.insert(v, v); + } + assert!(map.is_split()); + for value in map.values_mut() { + *value = (*value) * 2 + } + let values: Vec<_> = map.values().cloned().collect(); + assert_eq!(values.len(), 8); + for v in 1..=8 { + let v = 2 * v; + assert!(values.contains(&v)); + } + } + + #[test] + fn test_find() { + let mut m = IncrHashMap::new(); + assert!(m.get(&1).is_none()); + m.insert(1, 2); + match m.get(&1) { + None => panic!(), + Some(v) => assert_eq!(*v, 2), + } + assert!(!m.is_split()); + + // now make it split + for i in 1..=7 { + m.insert(1000 + i, i); + } + assert!(m.is_split()); + match m.get(&1) { + None => panic!(), + Some(v) => assert_eq!(*v, 2), + } + + // now make it un-split + m.insert(1000 + 8, 8); + assert!(!m.is_split()); + match m.get(&1) { + None => panic!(), + Some(v) => assert_eq!(*v, 2), + } + } + + #[test] + fn test_eq() { + let mut m1 = IncrHashMap::new(); + for (k, v) in (1..).zip('a'..).take(8) { + m1.insert(k, v); + } + + let mut m2 = IncrHashMap::new(); + for (k, v) in (1..).zip('a'..).take(7) { + m2.insert(k, v); + } + + assert!(m1 != m2); + + m2.insert(8, 'h'); + + assert_eq!(m1, m2); + } + + #[test] + fn test_show() { + let mut map = IncrHashMap::new(); + let empty: IncrHashMap = IncrHashMap::new(); + + map.insert(1, 2); + map.insert(3, 4); + + let map_str = format!("{:?}", map); + + assert!(map_str == "{1: 2, 3: 4}" || map_str == "{3: 4, 1: 2}"); + assert_eq!(format!("{:?}", empty), "{}"); + } + + #[test] + fn test_expand() { + let mut m = IncrHashMap::new(); + + assert_eq!(m.len(), 0); + assert!(m.is_empty()); + + let mut i = 0; + let old_raw_cap = m.raw_capacity(); + while old_raw_cap == m.raw_capacity() { + m.insert(i, i); + i += 1; + } + + assert_eq!(m.len(), i); + assert!(!m.is_empty()); + } + + #[test] + fn test_from_iter() { + let xs = (0..8).map(|v| (v, v)); + + let map: IncrHashMap<_, _> = xs.clone().collect(); + + for (k, v) in xs.clone() { + assert_eq!(map.get(&k), Some(&v)); + } + + assert_eq!(map.iter().len(), xs.len()); + } + + #[test] + fn test_size_hint() { + let xs = (0..8).map(|v| (v, v)); + + let map: IncrHashMap<_, _> = xs.clone().collect(); + + let mut iter = map.iter(); + + for _ in iter.by_ref().take(3) {} + + assert_eq!(iter.size_hint(), (8 - 3, Some(8 - 3))); + } + + #[test] + fn test_iter_len() { + let xs = (0..8).map(|v| (v, v)); + + let map: IncrHashMap<_, _> = xs.clone().collect(); + + let mut iter = map.iter(); + + for _ in iter.by_ref().take(3) {} + + assert_eq!(iter.len(), 8 - 3); + } + + #[test] + fn test_mut_size_hint() { + let xs = (0..8).map(|v| (v, v)); + + let mut map: IncrHashMap<_, _> = xs.clone().collect(); + + let mut iter = map.iter_mut(); + + for _ in iter.by_ref().take(3) {} + + assert_eq!(iter.size_hint(), (8 - 3, Some(8 - 3))); + } + + #[test] + fn test_iter_mut_len() { + let xs = (0..8).map(|v| (v, v)); + + let mut map: IncrHashMap<_, _> = xs.clone().collect(); + + let mut iter = map.iter_mut(); + + for _ in iter.by_ref().take(3) {} + + assert_eq!(iter.len(), 8 - 3); + } + + #[test] + fn test_index() { + let mut map = IncrHashMap::new(); + + for i in 1..=8 { + map.insert(i, i); + } + assert!(map.is_split()); + + assert_eq!(map[&2], 2); + } + + #[test] + #[should_panic] + fn test_index_nonexistent() { + let mut map = IncrHashMap::new(); + + for i in 1..=8 { + map.insert(i, i); + } + assert!(map.is_split()); + + map[&9]; + } + + #[test] + fn test_capacity_not_less_than_len() { + let mut a = IncrHashMap::new(); + let mut item = 0; + + for _ in 0..116 { + a.insert(item, 0); + item += 1; + } + + assert!(a.capacity() > a.len()); + + let free = a.capacity() - a.len(); + for _ in 0..free { + a.insert(item, 0); + item += 1; + } + + assert_eq!(a.len(), a.capacity()); + + // Insert at capacity should cause allocation. + a.insert(item, 0); + assert!(a.capacity() > a.len()); + } +} diff --git a/src/lib.rs b/src/lib.rs index e980829f3c..6a87ef76b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,7 @@ pub mod raw { mod raw; mod external_trait_impls; +mod incremental; mod map; #[cfg(feature = "rustc-internal-api")] mod rustc_entry; @@ -105,6 +106,7 @@ pub mod hash_set { } } +pub use crate::incremental::IncrHashMap; pub use crate::map::HashMap; pub use crate::set::HashSet; diff --git a/src/raw/mod.rs b/src/raw/mod.rs index e9272eca41..3e65c62519 100644 --- a/src/raw/mod.rs +++ b/src/raw/mod.rs @@ -675,6 +675,11 @@ impl RawTable { } } + #[cfg_attr(feature = "inline-more", inline)] + pub(crate) fn full_capacity(&self) -> usize { + bucket_mask_to_capacity(self.bucket_mask) + } + /// Out-of-line slow path for `reserve` and `try_reserve`. #[cold] #[inline(never)] @@ -689,7 +694,7 @@ impl RawTable { .checked_add(additional) .ok_or_else(|| fallability.capacity_overflow())?; - let full_capacity = bucket_mask_to_capacity(self.bucket_mask); + let full_capacity = self.full_capacity(); if new_items <= full_capacity / 2 { // Rehash in-place without re-allocating if we have plenty of spare // capacity that is locked up due to DELETED entries. From 0c608bb1f011204397a3258779756054245dddbe Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Mon, 29 Jun 2020 16:56:43 -0400 Subject: [PATCH 2/2] Make clippy happy --- src/incremental.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/incremental.rs b/src/incremental.rs index 857cf73fb1..e5e8be386e 100644 --- a/src/incremental.rs +++ b/src/incremental.rs @@ -430,7 +430,7 @@ impl IncrHashMap { /// ``` #[cfg_attr(feature = "inline-more", inline)] pub fn len(&self) -> usize { - self.table.len() + self.leftovers.as_ref().map(|t| t.table.len()).unwrap_or(0) + self.table.len() + self.leftovers.as_ref().map_or(0, |t| t.table.len()) } /// Returns `true` if the map contains no elements. @@ -959,7 +959,7 @@ impl IntoIter { pub(super) fn iter(&self) -> Iter<'_, K, V> { Iter { table: self.table.iter(), - leftovers: self.leftovers.as_ref().map(|i| i.iter()), + leftovers: self.leftovers.as_ref().map(RawIntoIter::iter), marker: PhantomData, } } @@ -1113,7 +1113,7 @@ impl<'a, K, V> Iterator for Iter<'a, K, V> { impl ExactSizeIterator for Iter<'_, K, V> { #[cfg_attr(feature = "inline-more", inline)] fn len(&self) -> usize { - self.table.len() + self.leftovers.as_ref().map(|lo| lo.len()).unwrap_or(0) + self.table.len() + self.leftovers.as_ref().map_or(0, |lo| lo.len()) } } @@ -1149,7 +1149,7 @@ impl<'a, K, V> Iterator for IterMut<'a, K, V> { impl ExactSizeIterator for IterMut<'_, K, V> { #[cfg_attr(feature = "inline-more", inline)] fn len(&self) -> usize { - self.table.len() + self.leftovers.as_ref().map(|lo| lo.len()).unwrap_or(0) + self.table.len() + self.leftovers.as_ref().map_or(0, |lo| lo.len()) } } impl FusedIterator for IterMut<'_, K, V> {} @@ -1188,7 +1188,7 @@ impl Iterator for IntoIter { impl ExactSizeIterator for IntoIter { #[cfg_attr(feature = "inline-more", inline)] fn len(&self) -> usize { - self.table.len() + self.leftovers.as_ref().map(|lo| lo.len()).unwrap_or(0) + self.table.len() + self.leftovers.as_ref().map_or(0, |lo| lo.len()) } } impl FusedIterator for IntoIter {}