Skip to content

Commit 67f704e

Browse files
committed
Auto merge of #555 - cuviper:set-no-raw_entry, r=Amanieu
Wean `HashSet` from the raw-entry API This changes `get_or_insert`, `get_or_insert_with`, and `bitxor_assign` to poke directly at the `RawTable` instead of using `raw_entry_mut()`. `HashSet::get_or_insert_with` also asserts that the converted value is actually equivalent after conversion, so we can ensure set consistency. `HashSet::get_or_insert_owned` is removed for now, since it offers no value over the `_with` method, as we would need to assert that too.
2 parents 3b350d7 + 6420cfd commit 67f704e

File tree

3 files changed

+109
-70
lines changed

3 files changed

+109
-70
lines changed

ci/run.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ if [ "${CHANNEL}" = "nightly" ]; then
4343
fi
4444

4545
# Make sure we can compile without the default hasher
46-
"${CARGO}" -vv build --target="${TARGET}" --no-default-features --features raw-entry
47-
"${CARGO}" -vv build --target="${TARGET}" --release --no-default-features --features raw-entry
46+
"${CARGO}" -vv build --target="${TARGET}" --no-default-features
47+
"${CARGO}" -vv build --target="${TARGET}" --release --no-default-features
4848

4949
"${CARGO}" -vv ${OP} --target="${TARGET}"
5050
"${CARGO}" -vv ${OP} --target="${TARGET}" --features "${FEATURES}"

src/map.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1741,11 +1741,7 @@ where
17411741
#[cfg_attr(feature = "inline-more", inline)]
17421742
pub fn insert(&mut self, k: K, v: V) -> Option<V> {
17431743
let hash = make_hash::<K, S>(&self.hash_builder, &k);
1744-
let hasher = make_hasher::<_, V, S>(&self.hash_builder);
1745-
match self
1746-
.table
1747-
.find_or_find_insert_slot(hash, equivalent_key(&k), hasher)
1748-
{
1744+
match self.find_or_find_insert_slot(hash, &k) {
17491745
Ok(bucket) => Some(mem::replace(unsafe { &mut bucket.as_mut().1 }, v)),
17501746
Err(slot) => {
17511747
unsafe {
@@ -1756,6 +1752,22 @@ where
17561752
}
17571753
}
17581754

1755+
#[cfg_attr(feature = "inline-more", inline)]
1756+
pub(crate) fn find_or_find_insert_slot<Q>(
1757+
&mut self,
1758+
hash: u64,
1759+
key: &Q,
1760+
) -> Result<Bucket<(K, V)>, crate::raw::InsertSlot>
1761+
where
1762+
Q: Equivalent<K> + ?Sized,
1763+
{
1764+
self.table.find_or_find_insert_slot(
1765+
hash,
1766+
equivalent_key(key),
1767+
make_hasher(&self.hash_builder),
1768+
)
1769+
}
1770+
17591771
/// Insert a key-value pair into the map without checking
17601772
/// if the key already exists in the map.
17611773
///

src/set.rs

Lines changed: 90 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
use crate::{Equivalent, TryReserveError};
2-
use alloc::borrow::ToOwned;
32
use core::hash::{BuildHasher, Hash};
43
use core::iter::{Chain, FusedIterator};
54
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Sub, SubAssign};
65
use core::{fmt, mem};
7-
use map::{equivalent_key, make_hash, make_hasher};
6+
use map::make_hash;
87

98
use super::map::{self, HashMap, Keys};
109
use crate::raw::{Allocator, Global, RawExtractIf};
@@ -911,45 +910,12 @@ where
911910
/// ```
912911
#[cfg_attr(feature = "inline-more", inline)]
913912
pub fn get_or_insert(&mut self, value: T) -> &T {
914-
// Although the raw entry gives us `&mut T`, we only return `&T` to be consistent with
915-
// `get`. Key mutation is "raw" because you're not supposed to affect `Eq` or `Hash`.
916-
self.map
917-
.raw_entry_mut()
918-
.from_key(&value)
919-
.or_insert(value, ())
920-
.0
921-
}
922-
923-
/// Inserts an owned copy of the given `value` into the set if it is not
924-
/// present, then returns a reference to the value in the set.
925-
///
926-
/// # Examples
927-
///
928-
/// ```
929-
/// use hashbrown::HashSet;
930-
///
931-
/// let mut set: HashSet<String> = ["cat", "dog", "horse"]
932-
/// .iter().map(|&pet| pet.to_owned()).collect();
933-
///
934-
/// assert_eq!(set.len(), 3);
935-
/// for &pet in &["cat", "dog", "fish"] {
936-
/// let value = set.get_or_insert_owned(pet);
937-
/// assert_eq!(value, pet);
938-
/// }
939-
/// assert_eq!(set.len(), 4); // a new "fish" was inserted
940-
/// ```
941-
#[inline]
942-
pub fn get_or_insert_owned<Q>(&mut self, value: &Q) -> &T
943-
where
944-
Q: Hash + Equivalent<T> + ToOwned<Owned = T> + ?Sized,
945-
{
946-
// Although the raw entry gives us `&mut T`, we only return `&T` to be consistent with
947-
// `get`. Key mutation is "raw" because you're not supposed to affect `Eq` or `Hash`.
948-
self.map
949-
.raw_entry_mut()
950-
.from_key(value)
951-
.or_insert_with(|| (value.to_owned(), ()))
952-
.0
913+
let hash = make_hash(&self.map.hash_builder, &value);
914+
let bucket = match self.map.find_or_find_insert_slot(hash, &value) {
915+
Ok(bucket) => bucket,
916+
Err(slot) => unsafe { self.map.table.insert_in_slot(hash, slot, (value, ())) },
917+
};
918+
unsafe { &bucket.as_ref().0 }
953919
}
954920

955921
/// Inserts a value computed from `f` into the set if the given `value` is
@@ -970,19 +936,29 @@ where
970936
/// }
971937
/// assert_eq!(set.len(), 4); // a new "fish" was inserted
972938
/// ```
939+
///
940+
/// The following example will panic because the new value doesn't match.
941+
///
942+
/// ```should_panic
943+
/// let mut set = hashbrown::HashSet::new();
944+
/// set.get_or_insert_with("rust", |_| String::new());
945+
/// ```
973946
#[cfg_attr(feature = "inline-more", inline)]
974947
pub fn get_or_insert_with<Q, F>(&mut self, value: &Q, f: F) -> &T
975948
where
976949
Q: Hash + Equivalent<T> + ?Sized,
977950
F: FnOnce(&Q) -> T,
978951
{
979-
// Although the raw entry gives us `&mut T`, we only return `&T` to be consistent with
980-
// `get`. Key mutation is "raw" because you're not supposed to affect `Eq` or `Hash`.
981-
self.map
982-
.raw_entry_mut()
983-
.from_key(value)
984-
.or_insert_with(|| (f(value), ()))
985-
.0
952+
let hash = make_hash(&self.map.hash_builder, value);
953+
let bucket = match self.map.find_or_find_insert_slot(hash, value) {
954+
Ok(bucket) => bucket,
955+
Err(slot) => {
956+
let new = f(value);
957+
assert!(value.equivalent(&new), "new value is not equivalent");
958+
unsafe { self.map.table.insert_in_slot(hash, slot, (new, ())) }
959+
}
960+
};
961+
unsafe { &bucket.as_ref().0 }
986962
}
987963

988964
/// Gets the given value's corresponding entry in the set for in-place manipulation.
@@ -1157,11 +1133,7 @@ where
11571133
#[cfg_attr(feature = "inline-more", inline)]
11581134
pub fn replace(&mut self, value: T) -> Option<T> {
11591135
let hash = make_hash(&self.map.hash_builder, &value);
1160-
match self.map.table.find_or_find_insert_slot(
1161-
hash,
1162-
equivalent_key(&value),
1163-
make_hasher(&self.map.hash_builder),
1164-
) {
1136+
match self.map.find_or_find_insert_slot(hash, &value) {
11651137
Ok(bucket) => Some(mem::replace(unsafe { &mut bucket.as_mut().0 }, value)),
11661138
Err(slot) => {
11671139
unsafe {
@@ -1607,15 +1579,17 @@ where
16071579
/// ```
16081580
fn bitxor_assign(&mut self, rhs: &HashSet<T, S, A>) {
16091581
for item in rhs {
1610-
let entry = self.map.raw_entry_mut().from_key(item);
1611-
match entry {
1612-
map::RawEntryMut::Occupied(e) => {
1613-
e.remove();
1614-
}
1615-
map::RawEntryMut::Vacant(e) => {
1616-
e.insert(item.to_owned(), ());
1617-
}
1618-
};
1582+
let hash = make_hash(&self.map.hash_builder, item);
1583+
match self.map.find_or_find_insert_slot(hash, item) {
1584+
Ok(bucket) => unsafe {
1585+
self.map.table.remove(bucket);
1586+
},
1587+
Err(slot) => unsafe {
1588+
self.map
1589+
.table
1590+
.insert_in_slot(hash, slot, (item.clone(), ()));
1591+
},
1592+
}
16191593
}
16201594
}
16211595
}
@@ -2598,7 +2572,7 @@ fn assert_covariance() {
25982572

25992573
#[cfg(test)]
26002574
mod test_set {
2601-
use super::HashSet;
2575+
use super::{make_hash, Equivalent, HashSet};
26022576
use crate::DefaultHashBuilder;
26032577
use std::vec::Vec;
26042578

@@ -3072,4 +3046,57 @@ mod test_set {
30723046
assert_eq!(HashSet::<u32>::new().allocation_size(), 0);
30733047
assert!(HashSet::<u32>::with_capacity(1).allocation_size() > core::mem::size_of::<u32>());
30743048
}
3049+
3050+
#[test]
3051+
fn duplicate_insert() {
3052+
let mut set = HashSet::new();
3053+
set.insert(1);
3054+
set.get_or_insert_with(&1, |_| 1);
3055+
set.get_or_insert_with(&1, |_| 1);
3056+
assert!([1].iter().eq(set.iter()));
3057+
}
3058+
3059+
#[test]
3060+
#[should_panic]
3061+
fn some_invalid_equivalent() {
3062+
use core::hash::{Hash, Hasher};
3063+
struct Invalid {
3064+
count: u32,
3065+
other: u32,
3066+
}
3067+
3068+
struct InvalidRef {
3069+
count: u32,
3070+
other: u32,
3071+
}
3072+
3073+
impl PartialEq for Invalid {
3074+
fn eq(&self, other: &Self) -> bool {
3075+
self.count == other.count && self.other == other.other
3076+
}
3077+
}
3078+
impl Eq for Invalid {}
3079+
3080+
impl Equivalent<Invalid> for InvalidRef {
3081+
fn equivalent(&self, key: &Invalid) -> bool {
3082+
self.count == key.count && self.other == key.other
3083+
}
3084+
}
3085+
impl Hash for Invalid {
3086+
fn hash<H: Hasher>(&self, state: &mut H) {
3087+
self.count.hash(state);
3088+
}
3089+
}
3090+
impl Hash for InvalidRef {
3091+
fn hash<H: Hasher>(&self, state: &mut H) {
3092+
self.count.hash(state);
3093+
}
3094+
}
3095+
let mut set: HashSet<Invalid> = HashSet::new();
3096+
let key = InvalidRef { count: 1, other: 1 };
3097+
let value = Invalid { count: 1, other: 2 };
3098+
if make_hash(set.hasher(), &key) == make_hash(set.hasher(), &value) {
3099+
set.get_or_insert_with(&key, |_| value);
3100+
}
3101+
}
30753102
}

0 commit comments

Comments
 (0)