From 23f54dbe5071381eeb93ba82d741311fdd7cd55a Mon Sep 17 00:00:00 2001 From: Alexander Schuetz Date: Sun, 21 Jun 2026 15:48:17 +0200 Subject: [PATCH] add various missing utility functions - insert_entry to RawVacantEntryMut and VacantEntry - front_entry/back_entry to map -> Option - current_entry to CursorMut -> Option --- src/linked_hash_map.rs | 152 +++++++++++++++++++++++++++++++++++++-- tests/linked_hash_map.rs | 137 +++++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+), 5 deletions(-) diff --git a/src/linked_hash_map.rs b/src/linked_hash_map.rs index 11cea17..dc6ca1e 100644 --- a/src/linked_hash_map.rs +++ b/src/linked_hash_map.rs @@ -271,6 +271,36 @@ where } } + #[inline] + pub fn back_entry(&mut self) -> Option> { + if self.is_empty() { + return None; + } + unsafe { + let last_key = (&*(*self.values.as_ptr()).links.value.prev.as_ptr()).key_ref(); + let RawEntryMut::Occupied(occu) = self.raw_entry_mut().from_key(last_key) else { + unreachable!("the back entry's key was not found in the hashtable") + }; + + Some(occu) + } + } + + #[inline] + pub fn front_entry(&mut self) -> Option> { + if self.is_empty() { + return None; + } + unsafe { + let first_key = (&*((*self.values.as_ptr()).links.value.next.as_ptr())).key_ref(); + let RawEntryMut::Occupied(occu) = self.raw_entry_mut().from_key(first_key) else { + unreachable!("the front entry's key was not found in the hashtable") + }; + + Some(occu) + } + } + #[inline] pub fn get(&self, k: &Q) -> Option<&V> where @@ -875,6 +905,16 @@ impl<'a, K, V, S> OccupiedEntry<'a, K, V, S> { self.raw_entry.cursor_mut() } + /// Returns a `RawOccupiedEntryMut` over the current entry. + #[inline] + pub fn raw_entry_mut(self) -> RawOccupiedEntryMut<'a, K, V, S> + where + K: Eq + Hash, + S: BuildHasher, + { + self.raw_entry + } + /// Replaces the entry's key with the key provided to `LinkedHashMap::entry`, and replaces the /// entry's value with the given `value` parameter. /// @@ -927,6 +967,30 @@ impl<'a, K, V, S> VacantEntry<'a, K, V, S> { { self.raw_entry.insert(self.key, value).1 } + + /// Insert's the key for this vacant entry paired with the given value as a new entry at the + /// *back* of the internal linked list. + /// This function then also then returns the OccupiedEntry pointing to the inserted element. + pub fn insert_entry(self, value: V) -> RawOccupiedEntryMut<'a, K, V, S> + where + K: Hash, + S: BuildHasher, + { + //We cannot return OccupiedEntry only RawOccupiedEntryMut because + //OccupiedEntry has api methods like replace_key which assume that we hold a copy of the key. + //We certainly do not hold a copy of the key anymore after inserting it. + self.raw_entry.insert_entry(self.key, value) + } + + /// Returns a `RawVacantEntryMut` over the current entry. + #[inline] + pub fn raw_entry_mut(self) -> RawVacantEntryMut<'a, K, V, S> + where + K: Eq + Hash, + S: BuildHasher, + { + self.raw_entry + } } pub struct RawEntryBuilder<'a, K, V, S> { @@ -1254,6 +1318,21 @@ impl<'a, K, V, S> RawVacantEntryMut<'a, K, V, S> { value: V, hasher: impl Fn(&K) -> u64, ) -> (&'a mut K, &'a mut V) + where + S: BuildHasher, + { + self.insert_entry_with_hasher(hash, key, value, hasher) + .into_key_value() + } + + #[inline] + pub fn insert_entry_with_hasher( + self, + hash: u64, + key: K, + value: V, + hasher: impl Fn(&K) -> u64, + ) -> RawOccupiedEntryMut<'a, K, V, S> where S: BuildHasher, { @@ -1266,13 +1345,41 @@ impl<'a, K, V, S> RawVacantEntryMut<'a, K, V, S> { let node = self .entry .into_table() - .insert_unique(hash, new_node, move |k| hasher((*k).as_ref().key_ref())) - .into_mut(); + .insert_unique(hash, new_node, move |k| hasher((*k).as_ref().key_ref())); - let (key, value) = (*node.as_ptr()).entry_mut(); - (key, value) + RawOccupiedEntryMut { + hash_builder: self.hash_builder, + free: self.free, + values: self.values, + entry: node, + } } } + + #[inline] + pub fn insert_entry_hashed_nocheck( + self, + hash: u64, + key: K, + value: V, + ) -> RawOccupiedEntryMut<'a, K, V, S> + where + K: Hash, + S: BuildHasher, + { + let hash_builder = self.hash_builder; + self.insert_entry_with_hasher(hash, key, value, |k| hash_key(hash_builder, k)) + } + + #[inline] + pub fn insert_entry(self, key: K, value: V) -> RawOccupiedEntryMut<'a, K, V, S> + where + K: Hash, + S: BuildHasher, + { + let hash = hash_key(self.hash_builder, &key); + self.insert_entry_hashed_nocheck(hash, key, value) + } } impl fmt::Debug for RawEntryBuilderMut<'_, K, V, S> { @@ -1732,7 +1839,7 @@ pub struct CursorMut<'a, K, V, S> { table: &'a mut hashbrown::HashTable>>, } -impl CursorMut<'_, K, V, S> { +impl<'a, K, V, S> CursorMut<'a, K, V, S> { /// Returns an `Option` of the current element in the list, provided it is not the /// _guard_ node, and `None` overwise. #[inline] @@ -1743,6 +1850,41 @@ impl CursorMut<'_, K, V, S> { } } + #[inline] + pub fn current_entry(self) -> Result, Self> + where + K: Eq + Hash, + S: BuildHasher, + { + unsafe { + let Some(values) = self.values else { + return Err(self); + }; + + if values.as_ptr() == self.cur { + return Err(self); + } + + let key = (*self.cur).key_ref(); + + let hash = hash_key(self.hash_builder, &key); + + let Ok(entry) = self + .table + .find_entry(hash, |o| (*o).as_ref().key_ref().eq(key)) + else { + unreachable!("current entry not found in hash table"); + }; + + Ok(RawOccupiedEntryMut { + hash_builder: self.hash_builder, + free: self.free, + values: self.values, + entry, + }) + } + } + /// Retrieves the next element in the list (moving towards the end). #[inline] pub fn peek_next(&mut self) -> Option<(&K, &mut V)> { diff --git a/tests/linked_hash_map.rs b/tests/linked_hash_map.rs index 1f2b401..a785176 100644 --- a/tests/linked_hash_map.rs +++ b/tests/linked_hash_map.rs @@ -5,6 +5,7 @@ use std::{ rc::Rc, }; +use hashlink::linked_hash_map::Entry; use hashlink::{LinkedHashMap, linked_hash_map}; #[allow(dead_code)] @@ -838,6 +839,142 @@ fn test_cursor_back_mut() { assert_eq!(cursor.current().unwrap().1, &mut 3); } +#[test] +fn test_vacant_entry_insert_entry() { + let mut map: LinkedHashMap = LinkedHashMap::new(); + + map.insert(1, 1); + map.insert(2, 2); + map.insert(3, 3); + + let Entry::Vacant(vacant) = map.entry(4) else { + panic!("no entry with 4 in map"); + }; + + let occu = vacant.insert_entry(55); + let (k, v) = occu.remove_entry(); + assert_eq!(k, 4); + assert_eq!(v, 55); + + assert!(map.get(&4).is_none()); + + let Entry::Vacant(vacant) = map.entry(4) else { + panic!("no entry with 4 in map"); + }; + + let occu = vacant.insert_entry(56); + assert_eq!(occu.get(), &56); + assert_eq!(occu.key(), &4); + + assert_eq!(map.get(&4), Some(&56)); + assert_eq!(map.back(), Some((&4, &56))); +} + +#[test] +fn test_front_back_entry() { + let mut map: LinkedHashMap = LinkedHashMap::new(); + assert!(map.front_entry().is_none()); + assert!(map.back_entry().is_none()); + + map.insert(1, 5); + map.insert(2, 6); + map.insert(3, 7); + + let Some(fe) = map.front_entry() else { + panic!("no front entry"); + }; + + assert_eq!(fe.key(), &1); + assert_eq!(fe.get(), &5); + + let Some(fe) = map.back_entry() else { + panic!("no back entry"); + }; + + assert_eq!(fe.key(), &3); + assert_eq!(fe.get(), &7); + + let Some(fe) = map.front_entry() else { + panic!("no front entry"); + }; + + assert_eq!(fe.remove_entry(), (1, 5)); + assert_eq!(map.front(), Some((&2, &6))); + assert_eq!(map.back(), Some((&3, &7))); + + let Some(fe) = map.back_entry() else { + panic!("no back entry"); + }; + + assert_eq!(fe.remove_entry(), (3, 7)); + assert_eq!(map.front(), Some((&2, &6))); + assert_eq!(map.back(), Some((&2, &6))); + assert_eq!(map.len(), 1); + + let Some(fe) = map.back_entry() else { + panic!("no back entry"); + }; + assert_eq!(fe.remove_entry(), (2, 6)); + assert!(map.is_empty()); +} + +#[test] +fn test_cursor_to_entry() { + let mut map: LinkedHashMap = LinkedHashMap::new(); + assert!(map.cursor_back_mut().current_entry().is_err()); + map.insert(1, 5); + map.insert(2, 6); + map.insert(3, 7); + + let e = map + .cursor_back_mut() + .current_entry() + .map_err(|_| ()) + .expect("map empty"); + assert_eq!(e.key(), &3); + assert_eq!(e.get(), &7); + + let mut cursor = map.cursor_back_mut(); + cursor.move_prev(); + let e = cursor.current_entry().map_err(|_| ()).expect("map empty"); + assert_eq!(e.key(), &2); + assert_eq!(e.get(), &6); + + let e = map + .cursor_front_mut() + .current_entry() + .map_err(|_| ()) + .expect("map empty"); + assert_eq!(e.key(), &1); + assert_eq!(e.get(), &5); + + let mut cursor = map.cursor_back_mut(); + cursor.move_prev(); + let e = cursor.current_entry().map_err(|_| ()).expect("map empty"); + assert_eq!(e.remove_entry(), (2, 6)); + + assert_eq!(map.len(), 2); + + let mut cursor = map.cursor_back_mut(); + cursor.move_prev(); + let e = cursor.current_entry().map_err(|_| ()).expect("map empty"); + assert_eq!(e.key(), &1); + assert_eq!(e.get(), &5); + + let mut cursor = map.cursor_back_mut(); + cursor.move_prev(); + cursor.move_prev(); + assert!(cursor.current_entry().is_err()); + + let mut cursor = map.cursor_front_mut(); + cursor.move_next(); + cursor.move_next(); + assert!(cursor.current_entry().is_err()); + + let collected = map.into_iter().collect::>(); + assert_eq!(collected, vec![(1, 5), (3, 7)]); +} + // Regression test for https://github.com/djc/hashlink/issues/43 // // A panic while dropping an entry during `clear` must not leave a moved-out node reachable