Skip to content

Commit 95a2cdd

Browse files
committed
Add HashTable methods related to the raw bucket index
1 parent 64aa7d5 commit 95a2cdd

File tree

2 files changed

+286
-0
lines changed

2 files changed

+286
-0
lines changed

src/raw/mod.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,43 @@ impl<T, A: Allocator> RawTable<T, A> {
12441244
}
12451245
}
12461246

1247+
/// Gets a reference to an element in the table at the given bucket index.
1248+
#[inline]
1249+
pub fn get_bucket(&self, index: usize) -> Option<&T> {
1250+
unsafe {
1251+
if index < self.buckets() && self.is_bucket_full(index) {
1252+
Some(self.bucket(index).as_ref())
1253+
} else {
1254+
None
1255+
}
1256+
}
1257+
}
1258+
1259+
/// Gets a mutable reference to an element in the table at the given bucket index.
1260+
#[inline]
1261+
pub fn get_bucket_mut(&mut self, index: usize) -> Option<&mut T> {
1262+
unsafe {
1263+
if index < self.buckets() && self.is_bucket_full(index) {
1264+
Some(self.bucket(index).as_mut())
1265+
} else {
1266+
None
1267+
}
1268+
}
1269+
}
1270+
1271+
/// Returns a pointer to an element in the table, but only after verifying that
1272+
/// the index is in-bounds and that its control byte matches the given hash.
1273+
#[inline]
1274+
pub fn checked_bucket(&self, hash: u64, index: usize) -> Option<Bucket<T>> {
1275+
unsafe {
1276+
if index < self.buckets() && *self.table.ctrl(index) == Tag::full(hash) {
1277+
Some(self.bucket(index))
1278+
} else {
1279+
None
1280+
}
1281+
}
1282+
}
1283+
12471284
/// Attempts to get mutable references to `N` entries in the table at once.
12481285
///
12491286
/// Returns an array of length `N` with the results of each query.

src/table.rs

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,55 @@ where
311311
}
312312
}
313313

314+
/// Returns the bucket index in the table for an entry with the given hash
315+
/// and which satisfies the equality function passed.
316+
///
317+
/// This can be used to store a borrow-free "reference" to the entry, later using
318+
/// [`get_bucket`][Self::get_bucket], [`get_bucket_mut`][Self::get_bucket_mut], or
319+
/// [`get_bucket_entry`][Self::get_bucket_entry] to access it again without hash probing.
320+
///
321+
/// The index is only meaningful as long as the table is not resized and no entries are added
322+
/// or removed. After such changes, it may end up pointing to a different entry or none at all.
323+
///
324+
/// # Examples
325+
///
326+
/// ```
327+
/// # #[cfg(feature = "nightly")]
328+
/// # fn test() {
329+
/// use hashbrown::{HashTable, DefaultHashBuilder};
330+
/// use std::hash::BuildHasher;
331+
///
332+
/// let mut table = HashTable::new();
333+
/// let hasher = DefaultHashBuilder::default();
334+
/// let hasher = |val: &_| hasher.hash_one(val);
335+
/// table.insert_unique(hasher(&1), (1, 1), |val| hasher(&val.0));
336+
/// table.insert_unique(hasher(&2), (2, 2), |val| hasher(&val.0));
337+
/// table.insert_unique(hasher(&3), (3, 3), |val| hasher(&val.0));
338+
///
339+
/// let index = table.find_bucket_index(hasher(&2), |val| val.0 == 2).unwrap();
340+
/// assert_eq!(table.get_bucket(index), Some(&(2, 2)));
341+
///
342+
/// // Mutation would invalidate any normal reference
343+
/// for (_key, value) in &mut table {
344+
/// *value *= 11;
345+
/// }
346+
///
347+
/// // The index still reaches the same key with the updated value
348+
/// assert_eq!(table.get_bucket(index), Some(&(2, 22)));
349+
/// # }
350+
/// # fn main() {
351+
/// # #[cfg(feature = "nightly")]
352+
/// # test()
353+
/// # }
354+
/// ```
355+
#[cfg_attr(feature = "inline-more", inline)]
356+
pub fn find_bucket_index(&self, hash: u64, eq: impl FnMut(&T) -> bool) -> Option<usize> {
357+
match self.raw.find(hash, eq) {
358+
Some(bucket) => Some(unsafe { self.raw.bucket_index(&bucket) }),
359+
None => None,
360+
}
361+
}
362+
314363
/// Returns an `Entry` for an entry in the table with the given hash
315364
/// and which satisfies the equality function passed.
316365
///
@@ -376,6 +425,121 @@ where
376425
}
377426
}
378427

428+
/// Returns an `OccupiedEntry` for a bucket index in the table with the given hash,
429+
/// or `None` if the index is out of bounds or if its hash doesn't match.
430+
///
431+
/// However, note that the hash is only compared for the few bits that are directly stored in
432+
/// the table, and even in full this could not guarantee equality. Use [`OccupiedEntry::get`]
433+
/// if you need to further validate a match.
434+
///
435+
/// # Examples
436+
///
437+
/// ```
438+
/// # #[cfg(feature = "nightly")]
439+
/// # fn test() {
440+
/// use hashbrown::{HashTable, DefaultHashBuilder};
441+
/// use std::hash::BuildHasher;
442+
///
443+
/// let mut table = HashTable::new();
444+
/// let hasher = DefaultHashBuilder::default();
445+
/// let hasher = |val: &_| hasher.hash_one(val);
446+
/// table.insert_unique(hasher(&1), (1, 'a'), |val| hasher(&val.0));
447+
/// table.insert_unique(hasher(&2), (2, 'b'), |val| hasher(&val.0));
448+
/// table.insert_unique(hasher(&3), (3, 'c'), |val| hasher(&val.0));
449+
///
450+
/// let hash = hasher(&2);
451+
/// let index = table.find_bucket_index(hash, |val| val.0 == 2).unwrap();
452+
///
453+
/// let bad_hash = !hash;
454+
/// assert!(table.get_bucket_entry(bad_hash, index).is_none());
455+
/// assert!(table.get_bucket_entry(hash, usize::MAX).is_none());
456+
///
457+
/// let occupied_entry = table.get_bucket_entry(hash, index).unwrap();
458+
/// assert_eq!(occupied_entry.get(), &(2, 'b'));
459+
/// assert_eq!(occupied_entry.remove().0, (2, 'b'));
460+
///
461+
/// assert!(table.find(hash, |val| val.0 == 2).is_none());
462+
/// # }
463+
/// # fn main() {
464+
/// # #[cfg(feature = "nightly")]
465+
/// # test()
466+
/// # }
467+
/// ```
468+
#[inline]
469+
pub fn get_bucket_entry(&mut self, hash: u64, index: usize) -> Option<OccupiedEntry<'_, T, A>> {
470+
Some(OccupiedEntry {
471+
hash,
472+
bucket: self.raw.checked_bucket(hash, index)?,
473+
table: self,
474+
})
475+
}
476+
477+
/// Gets a reference to an entry in the table at the given bucket index,
478+
/// or `None` if it is unoccupied or out of bounds.
479+
///
480+
/// # Examples
481+
///
482+
/// ```
483+
/// # #[cfg(feature = "nightly")]
484+
/// # fn test() {
485+
/// use hashbrown::{HashTable, DefaultHashBuilder};
486+
/// use std::hash::BuildHasher;
487+
///
488+
/// let mut table = HashTable::new();
489+
/// let hasher = DefaultHashBuilder::default();
490+
/// let hasher = |val: &_| hasher.hash_one(val);
491+
/// table.insert_unique(hasher(&1), (1, 'a'), |val| hasher(&val.0));
492+
/// table.insert_unique(hasher(&2), (2, 'b'), |val| hasher(&val.0));
493+
/// table.insert_unique(hasher(&3), (3, 'c'), |val| hasher(&val.0));
494+
///
495+
/// let index = table.find_bucket_index(hasher(&2), |val| val.0 == 2).unwrap();
496+
/// assert_eq!(table.get_bucket(index), Some(&(2, 'b')));
497+
/// # }
498+
/// # fn main() {
499+
/// # #[cfg(feature = "nightly")]
500+
/// # test()
501+
/// # }
502+
/// ```
503+
#[inline]
504+
pub fn get_bucket(&self, index: usize) -> Option<&T> {
505+
self.raw.get_bucket(index)
506+
}
507+
508+
/// Gets a mutable reference to an entry in the table at the given bucket index,
509+
/// or `None` if it is unoccupied or out of bounds.
510+
///
511+
/// # Examples
512+
///
513+
/// ```
514+
/// # #[cfg(feature = "nightly")]
515+
/// # fn test() {
516+
/// use hashbrown::{HashTable, DefaultHashBuilder};
517+
/// use std::hash::BuildHasher;
518+
///
519+
/// let mut table = HashTable::new();
520+
/// let hasher = DefaultHashBuilder::default();
521+
/// let hasher = |val: &_| hasher.hash_one(val);
522+
/// table.insert_unique(hasher(&1), (1, 'a'), |val| hasher(&val.0));
523+
/// table.insert_unique(hasher(&2), (2, 'b'), |val| hasher(&val.0));
524+
/// table.insert_unique(hasher(&3), (3, 'c'), |val| hasher(&val.0));
525+
///
526+
/// let index = table.find_bucket_index(hasher(&2), |val| val.0 == 2).unwrap();
527+
/// assert_eq!(table.get_bucket(index), Some(&(2, 'b')));
528+
/// if let Some((_key, value)) = table.get_bucket_mut(index) {
529+
/// *value = 'B';
530+
/// }
531+
/// assert_eq!(table.get_bucket(index), Some(&(2, 'B')));
532+
/// # }
533+
/// # fn main() {
534+
/// # #[cfg(feature = "nightly")]
535+
/// # test()
536+
/// # }
537+
/// ```
538+
#[inline]
539+
pub fn get_bucket_mut(&mut self, index: usize) -> Option<&mut T> {
540+
self.raw.get_bucket_mut(index)
541+
}
542+
379543
/// Inserts an element into the `HashTable` with the given hash value, but
380544
/// without checking whether an equivalent element already exists within the
381545
/// table.
@@ -591,6 +755,44 @@ where
591755
self.raw.try_reserve(additional, hasher)
592756
}
593757

758+
/// Returns the raw number of buckets allocated in the table.
759+
///
760+
/// This is an upper bound on any methods that take or return a bucket index,
761+
/// as opposed to the usable [`capacity`](Self::capacity) for entries which is
762+
/// reduced by an unspecified load factor.
763+
///
764+
/// # Examples
765+
///
766+
/// ```
767+
/// # #[cfg(feature = "nightly")]
768+
/// # fn test() {
769+
/// use hashbrown::{HashTable, DefaultHashBuilder};
770+
/// use std::hash::BuildHasher;
771+
///
772+
/// let mut table = HashTable::new();
773+
/// let hasher = DefaultHashBuilder::default();
774+
/// let hasher = |val: &_| hasher.hash_one(val);
775+
/// table.insert_unique(hasher(&1), (1, 'a'), |val| hasher(&val.0));
776+
/// table.insert_unique(hasher(&2), (2, 'b'), |val| hasher(&val.0));
777+
/// table.insert_unique(hasher(&3), (3, 'c'), |val| hasher(&val.0));
778+
///
779+
/// // Each entry is available at some index in the bucket range.
780+
/// let count = (0..table.buckets())
781+
/// .filter_map(|i| table.get_bucket(i))
782+
/// .count();
783+
/// assert_eq!(count, 3);
784+
///
785+
/// assert_eq!(table.get_bucket(table.buckets()), None);
786+
/// # }
787+
/// # fn main() {
788+
/// # #[cfg(feature = "nightly")]
789+
/// # test()
790+
/// # }
791+
/// ```
792+
pub fn buckets(&self) -> usize {
793+
self.raw.buckets()
794+
}
795+
594796
/// Returns the number of elements the table can hold without reallocating.
595797
///
596798
/// # Examples
@@ -1789,6 +1991,53 @@ where
17891991
pub fn into_table(self) -> &'a mut HashTable<T, A> {
17901992
self.table
17911993
}
1994+
1995+
/// Returns the bucket index in the table for this entry.
1996+
///
1997+
/// This can be used to store a borrow-free "reference" to the entry, later using
1998+
/// [`HashTable::get_bucket`], [`HashTable::get_bucket_mut`], or
1999+
/// [`HashTable::get_bucket_entry`] to access the it again without hash probing.
2000+
///
2001+
/// The index is only meaningful as long as the table is not resized and no entries are added
2002+
/// or removed. After such changes, it may end up pointing to a different entry or none at all.
2003+
///
2004+
/// # Examples
2005+
///
2006+
/// ```
2007+
/// # #[cfg(feature = "nightly")]
2008+
/// # fn test() {
2009+
/// use hashbrown::{HashTable, DefaultHashBuilder};
2010+
/// use std::hash::BuildHasher;
2011+
///
2012+
/// let mut table = HashTable::new();
2013+
/// let hasher = DefaultHashBuilder::default();
2014+
/// let hasher = |val: &_| hasher.hash_one(val);
2015+
/// table.insert_unique(hasher(&1), (1, 1), |val| hasher(&val.0));
2016+
/// table.insert_unique(hasher(&2), (2, 2), |val| hasher(&val.0));
2017+
/// table.insert_unique(hasher(&3), (3, 3), |val| hasher(&val.0));
2018+
///
2019+
/// let index = table
2020+
/// .entry(hasher(&2), |val| val.0 == 2, |val| hasher(&val.0))
2021+
/// .or_insert((2, -2))
2022+
/// .bucket_index();
2023+
/// assert_eq!(table.get_bucket(index), Some(&(2, 2)));
2024+
///
2025+
/// // Full mutation would invalidate any normal reference
2026+
/// for (_key, value) in &mut table {
2027+
/// *value *= 11;
2028+
/// }
2029+
///
2030+
/// // The index still reaches the same key with the updated value
2031+
/// assert_eq!(table.get_bucket(index), Some(&(2, 22)));
2032+
/// # }
2033+
/// # fn main() {
2034+
/// # #[cfg(feature = "nightly")]
2035+
/// # test()
2036+
/// # }
2037+
/// ```
2038+
pub fn bucket_index(&self) -> usize {
2039+
unsafe { self.table.raw.bucket_index(&self.bucket) }
2040+
}
17922041
}
17932042

17942043
/// A view into a vacant entry in a `HashTable`.

0 commit comments

Comments
 (0)