From 9340453989c56cc2bd6a2393972a872465537431 Mon Sep 17 00:00:00 2001 From: yyny <6576327+yyny@users.noreply.github.com> Date: Tue, 14 Feb 2023 23:43:09 +0100 Subject: [PATCH 1/2] Add `HashSet::get_or_insert_with_mut` This function avoids unnecessairy work when the value to be stored can take ownership of the lookup key, such as when interning. It makes a stronger guarantee than `HashSet::get_or_insert_with`, since the reference given to `f` is guaranteed to be unique, and thus safe to mutate. The functionality of this function can already be achieved by (ab)using `UnsafeCell`, but that relies on the implementation detail that the `&Q` given to `f` is unique. --- src/set.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/set.rs b/src/set.rs index ce794ce50a..22773e56ae 100644 --- a/src/set.rs +++ b/src/set.rs @@ -983,6 +983,45 @@ where .0 } + /// Inserts a value computed from `f` into the set if the given `value` is + /// not present, then returns a reference to the value in the set. + /// + /// # Examples + /// + /// ``` + /// use hashbrown::HashSet; + /// + /// let mut set: HashSet = ["cat", "dog", "horse"] + /// .iter().map(|&pet| pet.to_owned()).collect(); + /// + /// assert_eq!(set.len(), 3); + /// { + /// let mut pet = String::from("cat"); + /// let value = set.get_or_insert_with_mut(&mut pet, |x| std::mem::take(x)); + /// assert!(!pet.is_empty()); // string contents were not taken because the value was present + /// } + /// { + /// let mut pet = String::from("fish"); + /// let value = set.get_or_insert_with_mut(&mut pet, |x| std::mem::take(x)); + /// assert!(pet.is_empty()); // string contents were taken + /// assert_eq!(set.len(), 4); // a new "fish" was inserted + /// } + /// ``` + #[cfg_attr(feature = "inline-more", inline)] + pub fn get_or_insert_with_mut(&mut self, value: &mut Q, f: F) -> &T + where + Q: Hash + Equivalent, + F: FnOnce(&mut Q) -> T, + { + // Although the raw entry gives us `&mut T`, we only return `&T` to be consistent with + // `get`. Key mutation is "raw" because you're not supposed to affect `Eq` or `Hash`. + self.map + .raw_entry_mut() + .from_key(value) + .or_insert_with(|| (f(value), ())) + .0 + } + /// Gets the given value's corresponding entry in the set for in-place manipulation. /// /// # Examples From 739a8bdb6c4e5f603d75aa81a86cadac37f75a9e Mon Sep 17 00:00:00 2001 From: yyny <6576327+yyny@users.noreply.github.com> Date: Thu, 16 Feb 2023 01:13:17 +0100 Subject: [PATCH 2/2] Document the expected invariants for `get_or_insert_with` and `get_or_insert_with_mut` --- src/set.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/set.rs b/src/set.rs index 22773e56ae..88e285edf6 100644 --- a/src/set.rs +++ b/src/set.rs @@ -951,7 +951,8 @@ where } /// Inserts a value computed from `f` into the set if the given `value` is - /// not present, then returns a reference to the value in the set. + /// not present, then returns a reference to the value in the set. The value + /// computed by `f` should have the same hash and compare equivalent to `value`. /// /// # Examples /// @@ -984,7 +985,8 @@ where } /// Inserts a value computed from `f` into the set if the given `value` is - /// not present, then returns a reference to the value in the set. + /// not present, then returns a reference to the value in the set. The value + /// computed by `f` should have the same hash and compare equivalent to `value`. /// /// # Examples ///