From 524982c0a68ca1d492f547dee36520bad389fa15 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Sun, 4 May 2025 14:44:28 -0600 Subject: [PATCH 1/3] feat: add HashTable::insert_unique_within_capacity --- src/raw/mod.rs | 41 ++++++++++++++++++++++++++++++++--------- src/table.rs | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/raw/mod.rs b/src/raw/mod.rs index c0d82a1b5..456523695 100644 --- a/src/raw/mod.rs +++ b/src/raw/mod.rs @@ -1045,18 +1045,15 @@ impl RawTable { ) } - /// Inserts a new element into the table, and returns its raw bucket. - /// - /// This does not check if the given element already exists in the table. - #[cfg_attr(feature = "inline-more", inline)] - pub fn insert(&mut self, hash: u64, value: T, hasher: impl Fn(&T) -> u64) -> Bucket { + #[inline(always)] + fn find_insert_slot(&self, hash: u64) -> Option { unsafe { // SAFETY: // 1. The [`RawTableInner`] must already have properly initialized control bytes since // we will never expose `RawTable::new_uninitialized` in a public API. // // 2. We reserve additional space (if necessary) right after calling this function. - let mut slot = self.table.find_insert_slot(hash); + let slot = self.table.find_insert_slot(hash); // We can avoid growing the table once we have reached our load factor if we are replacing // a tombstone. This works since the number of EMPTY slots does not change in this case. @@ -1065,14 +1062,40 @@ impl RawTable { // in the range `0..=self.buckets()`. let old_ctrl = *self.table.ctrl(slot.index); if unlikely(self.table.growth_left == 0 && old_ctrl.special_is_empty()) { + None + } else { + Some(slot) + } + } + } + + /// Inserts a new element into the table, and returns its raw bucket. + /// + /// This does not check if the given element already exists in the table. + #[cfg_attr(feature = "inline-more", inline)] + pub fn insert(&mut self, hash: u64, value: T, hasher: impl Fn(&T) -> u64) -> Bucket { + let slot = match self.find_insert_slot(hash) { + None => { self.reserve(1, hasher); // SAFETY: We know for sure that `RawTableInner` has control bytes // initialized and that there is extra space in the table. - slot = self.table.find_insert_slot(hash); + unsafe { self.table.find_insert_slot(hash) } } + Some(slot) => slot, + }; + // SAFETY: todo + unsafe { self.insert_in_slot(hash, slot, value) } + } - self.insert_in_slot(hash, slot, value) - } + /// Inserts a new element into the table if there is capacity, and returns + /// its raw bucket. + /// + /// This does not check if the given element already exists in the table. + #[inline] + pub(crate) fn insert_within_capacity(&mut self, hash: u64, value: T) -> Option> { + let slot = self.find_insert_slot(hash)?; + // SAFETY: todo + Some(unsafe { self.insert_in_slot(hash, slot, value) }) } /// Inserts a new element into the table, and returns a mutable reference to it. diff --git a/src/table.rs b/src/table.rs index 2565f6f8b..7a383343b 100644 --- a/src/table.rs +++ b/src/table.rs @@ -415,6 +415,44 @@ where } } + /// Inserts an element into the `HashTable` with the given hash value, but + /// without checking whether an equivalent element already exists within + /// the table. If there is insufficient capacity, then this returns None. + /// + /// # Examples + /// + /// ``` + /// # #[cfg(feature = "nightly")] + /// # fn test() { + /// use hashbrown::{HashTable, DefaultHashBuilder}; + /// use std::hash::BuildHasher; + /// + /// let mut v = HashTable::new(); + /// let hasher = DefaultHashBuilder::default(); + /// let hasher = |val: &_| hasher.hash_one(val); + /// assert!(v.insert_unique_within_capacity(hasher(&1), 1).is_none()); + /// v.reserve(1, hasher); + /// v.insert_unique_within_capacity(hasher(&1), 1); + /// assert!(!v.is_empty()); + /// # } + /// # fn main() { + /// # #[cfg(feature = "nightly")] + /// # test() + /// # } + /// ``` + pub fn insert_unique_within_capacity( + &mut self, + hash: u64, + value: T, + ) -> Option> { + let bucket = self.raw.insert_within_capacity(hash, value)?; + Some(OccupiedEntry { + hash, + bucket, + table: self, + }) + } + /// Clears the table, removing all values. /// /// # Examples From 41881299a1eecee71efaf88197b191dba507f61c Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Mon, 5 May 2025 08:06:20 -0600 Subject: [PATCH 2/3] style: return Result instead of Option --- src/raw/mod.rs | 10 ++++++---- src/table.rs | 24 ++++++++++++++---------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/raw/mod.rs b/src/raw/mod.rs index 456523695..8afab8fd5 100644 --- a/src/raw/mod.rs +++ b/src/raw/mod.rs @@ -1092,10 +1092,12 @@ impl RawTable { /// /// This does not check if the given element already exists in the table. #[inline] - pub(crate) fn insert_within_capacity(&mut self, hash: u64, value: T) -> Option> { - let slot = self.find_insert_slot(hash)?; - // SAFETY: todo - Some(unsafe { self.insert_in_slot(hash, slot, value) }) + pub(crate) fn insert_within_capacity(&mut self, hash: u64, value: T) -> Result, T> { + match self.find_insert_slot(hash) { + // SAFETY: todo + Some(slot) => Ok(unsafe { self.insert_in_slot(hash, slot, value) }), + None => Err(value), + } } /// Inserts a new element into the table, and returns a mutable reference to it. diff --git a/src/table.rs b/src/table.rs index 7a383343b..1f0c022b8 100644 --- a/src/table.rs +++ b/src/table.rs @@ -417,7 +417,8 @@ where /// Inserts an element into the `HashTable` with the given hash value, but /// without checking whether an equivalent element already exists within - /// the table. If there is insufficient capacity, then this returns None. + /// the table. If there is insufficient capacity, then this returns an + /// error holding the provided value. /// /// # Examples /// @@ -430,9 +431,10 @@ where /// let mut v = HashTable::new(); /// let hasher = DefaultHashBuilder::default(); /// let hasher = |val: &_| hasher.hash_one(val); - /// assert!(v.insert_unique_within_capacity(hasher(&1), 1).is_none()); + /// assert!(v.insert_unique_within_capacity(hasher(&1), 1).is_err()); + /// assert!(v.is_empty()); /// v.reserve(1, hasher); - /// v.insert_unique_within_capacity(hasher(&1), 1); + /// assert!(v.insert_unique_within_capacity(hasher(&1), 1).is_ok()); /// assert!(!v.is_empty()); /// # } /// # fn main() { @@ -444,13 +446,15 @@ where &mut self, hash: u64, value: T, - ) -> Option> { - let bucket = self.raw.insert_within_capacity(hash, value)?; - Some(OccupiedEntry { - hash, - bucket, - table: self, - }) + ) -> Result, T> { + match self.raw.insert_within_capacity(hash, value) { + Ok(bucket) => Ok(OccupiedEntry { + hash, + bucket, + table: self, + }), + Err(err) => Err(err), + } } /// Clears the table, removing all values. From 7237c292ace8f9f1762d671555b15b50425295f3 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 6 May 2025 12:18:14 -0600 Subject: [PATCH 3/3] style: add try_ prefix to insert_unique_within_capacity --- src/raw/mod.rs | 11 ++++++++--- src/table.rs | 8 ++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/raw/mod.rs b/src/raw/mod.rs index 8afab8fd5..409ddae57 100644 --- a/src/raw/mod.rs +++ b/src/raw/mod.rs @@ -1087,12 +1087,17 @@ impl RawTable { unsafe { self.insert_in_slot(hash, slot, value) } } - /// Inserts a new element into the table if there is capacity, and returns - /// its raw bucket. + /// Tries to insert a new element into the table if there is capacity. + /// Returns its raw bucket if successful, and otherwise returns `value` + /// to the caller on error. /// /// This does not check if the given element already exists in the table. #[inline] - pub(crate) fn insert_within_capacity(&mut self, hash: u64, value: T) -> Result, T> { + pub(crate) fn try_insert_within_capacity( + &mut self, + hash: u64, + value: T, + ) -> Result, T> { match self.find_insert_slot(hash) { // SAFETY: todo Some(slot) => Ok(unsafe { self.insert_in_slot(hash, slot, value) }), diff --git a/src/table.rs b/src/table.rs index 1f0c022b8..aeac5906d 100644 --- a/src/table.rs +++ b/src/table.rs @@ -431,10 +431,10 @@ where /// let mut v = HashTable::new(); /// let hasher = DefaultHashBuilder::default(); /// let hasher = |val: &_| hasher.hash_one(val); - /// assert!(v.insert_unique_within_capacity(hasher(&1), 1).is_err()); + /// assert!(v.try_insert_unique_within_capacity(hasher(&1), 1).is_err()); /// assert!(v.is_empty()); /// v.reserve(1, hasher); - /// assert!(v.insert_unique_within_capacity(hasher(&1), 1).is_ok()); + /// assert!(v.try_insert_unique_within_capacity(hasher(&1), 1).is_ok()); /// assert!(!v.is_empty()); /// # } /// # fn main() { @@ -442,12 +442,12 @@ where /// # test() /// # } /// ``` - pub fn insert_unique_within_capacity( + pub fn try_insert_unique_within_capacity( &mut self, hash: u64, value: T, ) -> Result, T> { - match self.raw.insert_within_capacity(hash, value) { + match self.raw.try_insert_within_capacity(hash, value) { Ok(bucket) => Ok(OccupiedEntry { hash, bucket,