use std::borrow::Borrow;
use std::fmt;
use std::hash::{BuildHasher, Hash};
use std::iter::FusedIterator;
use std::marker::PhantomData;
use std::mem;
use std::ops::Range;
use std::ptr;
use crate::common::config::{DEFAULT_RESERVE_FRACTION, INITIAL_CAPACITY};
use crate::common::control::{CTRL_EMPTY, CTRL_TOMBSTONE, ControlByte, ControlOps};
use crate::common::entry::{EntryView, OccupiedError as CommonOccupiedError};
use crate::common::iter::{
IntoKeys as CommonIntoKeys, IntoValues as CommonIntoValues, Keys as CommonKeys,
Values as CommonValues,
};
use crate::common::layout::{Entry as SlotEntry, GROUP_SIZE, RawTable, try_zeroed_boxed_slice};
use crate::common::math::{
capacity_for, ceil_to_usize, fastmod_magic, fastmod_u32, floor_to_usize, level_salt,
max_insertions, round_to_usize, round_up_to_group, round_up_to_pow2_groups,
sanitize_reserve_fraction, usize_to_f64,
};
use crate::common::simd::{ProbeOps, prefetch_read};
use crate::common::{DefaultHashBuilder, TryReserveError};
pub(crate) const MAX_FUNNEL_RESERVE_FRACTION: f64 = 1.0 / 8.0;
#[derive(Debug, Clone, Copy)]
pub struct FunnelOptions {
capacity: usize,
reserve_fraction: f64,
primary_probe_limit: Option<usize>,
}
impl Default for FunnelOptions {
fn default() -> Self {
Self {
capacity: 0,
reserve_fraction: DEFAULT_RESERVE_FRACTION,
primary_probe_limit: None,
}
}
}
impl FunnelOptions {
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
capacity,
..Self::default()
}
}
#[must_use]
pub fn capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity;
self
}
#[must_use]
pub fn reserve_fraction(mut self, reserve_fraction: f64) -> Self {
self.reserve_fraction = reserve_fraction;
self
}
#[must_use]
pub fn primary_probe_limit(mut self, primary_probe_limit: usize) -> Self {
self.primary_probe_limit = Some(primary_probe_limit);
self
}
}
struct BucketLevel<K, V> {
table: RawTable<SlotEntry<K, V>>,
len: usize,
tombstones: usize,
bucket_size: usize,
bucket_count: usize,
salt: u64,
bucket_count_magic: u64,
}
impl<K, V> BucketLevel<K, V> {
fn with_bucket_count(bucket_count: usize, bucket_size: usize, salt: u64) -> Self {
let total_capacity = bucket_count.saturating_mul(bucket_size);
let bucket_count_magic = if bucket_count > 1 {
fastmod_magic(bucket_count)
} else {
0
};
Self {
table: RawTable::new(total_capacity),
len: 0,
tombstones: 0,
bucket_size,
bucket_count,
salt,
bucket_count_magic,
}
}
fn try_with_bucket_count(
bucket_count: usize,
bucket_size: usize,
salt: u64,
) -> Result<Self, TryReserveError> {
let total_capacity = bucket_count.saturating_mul(bucket_size);
let bucket_count_magic = if bucket_count > 1 {
fastmod_magic(bucket_count)
} else {
0
};
let table = RawTable::try_new(total_capacity).map_err(|()| TryReserveError::AllocError)?;
Ok(Self {
table,
len: 0,
tombstones: 0,
bucket_size,
bucket_count,
salt,
bucket_count_magic,
})
}
#[inline]
fn capacity(&self) -> usize {
self.table.capacity()
}
#[inline]
fn bucket_index(&self, key_hash: u64) -> usize {
fastmod_u32(
key_hash ^ self.salt,
self.bucket_count_magic,
self.bucket_count,
)
}
#[inline]
fn bucket_range(&self, bucket_idx: usize) -> Range<usize> {
let start = bucket_idx * self.bucket_size;
start..start + self.bucket_size
}
}
impl<K, V> Drop for BucketLevel<K, V> {
fn drop(&mut self) {
for idx in 0..self.table.capacity() {
if self.table.control_at(idx).is_occupied() {
unsafe { self.table.drop_in_place(idx) };
}
}
}
}
struct SpecialPrimary<K, V> {
table: RawTable<SlotEntry<K, V>>,
len: usize,
tombstones: usize,
group_count_mask: usize,
group_summaries: Box<[u128]>,
}
impl<K, V> SpecialPrimary<K, V> {
fn with_capacity(capacity: usize) -> Self {
let inflated = round_up_to_pow2_groups(capacity);
let table = RawTable::new(inflated);
let group_count = table.group_count();
debug_assert!(
group_count == 0 || group_count.is_power_of_two(),
"SpecialPrimary group_count must be pow2",
);
Self {
table,
len: 0,
tombstones: 0,
group_count_mask: group_count.saturating_sub(1),
group_summaries: vec![0; group_count].into_boxed_slice(),
}
}
fn try_with_capacity(capacity: usize) -> Result<Self, TryReserveError> {
let inflated = round_up_to_pow2_groups(capacity);
let table = RawTable::try_new(inflated).map_err(|()| TryReserveError::AllocError)?;
let group_count = table.group_count();
let group_summaries = try_zeroed_boxed_slice::<u128>(group_count)?;
Ok(Self {
table,
len: 0,
tombstones: 0,
group_count_mask: group_count.saturating_sub(1),
group_summaries,
})
}
}
impl<K, V> Drop for SpecialPrimary<K, V> {
fn drop(&mut self) {
for idx in 0..self.table.capacity() {
if self.table.control_at(idx).is_occupied() {
unsafe { self.table.drop_in_place(idx) };
}
}
}
}
struct SpecialFallback<K, V> {
table: RawTable<SlotEntry<K, V>>,
len: usize,
tombstones: usize,
bucket_size: usize,
bucket_count: usize,
}
impl<K, V> SpecialFallback<K, V> {
fn with_capacity(capacity: usize, bucket_size: usize) -> Self {
let bucket_count = if bucket_size == 0 {
0
} else {
capacity.div_ceil(bucket_size)
};
Self {
table: RawTable::new(capacity),
len: 0,
tombstones: 0,
bucket_size,
bucket_count,
}
}
fn try_with_capacity(capacity: usize, bucket_size: usize) -> Result<Self, TryReserveError> {
let bucket_count = if bucket_size == 0 {
0
} else {
capacity.div_ceil(bucket_size)
};
let table = RawTable::try_new(capacity).map_err(|()| TryReserveError::AllocError)?;
Ok(Self {
table,
len: 0,
tombstones: 0,
bucket_size,
bucket_count,
})
}
#[inline]
fn capacity(&self) -> usize {
self.table.capacity()
}
#[inline]
fn bucket_count(&self) -> usize {
self.bucket_count
}
#[inline]
fn bucket_range(&self, bucket_idx: usize) -> Range<usize> {
let start = bucket_idx * self.bucket_size;
let end = (start + self.bucket_size).min(self.table.capacity());
start..end
}
}
impl<K, V> Drop for SpecialFallback<K, V> {
fn drop(&mut self) {
for idx in 0..self.table.capacity() {
if self.table.control_at(idx).is_occupied() {
unsafe { self.table.drop_in_place(idx) };
}
}
}
}
struct SpecialArray<K, V> {
primary: SpecialPrimary<K, V>,
fallback: SpecialFallback<K, V>,
}
impl<K, V> SpecialArray<K, V> {
fn with_capacity(capacity: usize, primary_probe_limit: usize) -> Self {
let fallback_bucket_size = (2usize.saturating_mul(primary_probe_limit)).max(2);
let primary_capacity = capacity.div_ceil(2);
let fallback_capacity = capacity.saturating_sub(primary_capacity);
Self {
primary: SpecialPrimary::with_capacity(primary_capacity),
fallback: SpecialFallback::with_capacity(fallback_capacity, fallback_bucket_size),
}
}
fn try_with_capacity(
capacity: usize,
primary_probe_limit: usize,
) -> Result<Self, TryReserveError> {
let fallback_bucket_size = (2usize.saturating_mul(primary_probe_limit)).max(2);
let primary_capacity = capacity.div_ceil(2);
let fallback_capacity = capacity.saturating_sub(primary_capacity);
Ok(Self {
primary: SpecialPrimary::try_with_capacity(primary_capacity)?,
fallback: SpecialFallback::try_with_capacity(fallback_capacity, fallback_bucket_size)?,
})
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum SlotLocation {
Level { level_idx: usize, slot_idx: usize },
SpecialPrimary { slot_idx: usize },
SpecialFallback { slot_idx: usize },
}
enum LookupStep {
Found(usize),
Continue,
StopSearch,
}
pub struct FunnelHashMap<K, V> {
levels: Vec<BucketLevel<K, V>>,
special: SpecialArray<K, V>,
len: usize,
capacity: usize,
max_insertions: usize,
reserve_fraction: f64,
primary_probe_limit: usize,
max_populated_level: usize,
hash_builder: DefaultHashBuilder,
}
impl<K: fmt::Debug, V: fmt::Debug> fmt::Debug for FunnelHashMap<K, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FunnelHashMap")
.field("len", &self.len)
.field("capacity", &self.capacity)
.field("max_populated_level", &self.max_populated_level)
.finish_non_exhaustive()
}
}
impl<K, V> Default for FunnelHashMap<K, V>
where
K: Eq + Hash,
{
fn default() -> Self {
Self::new()
}
}
impl<K, V> FunnelHashMap<K, V>
where
K: Eq + Hash,
{
#[must_use]
pub fn new() -> Self {
Self::with_options(FunnelOptions::default())
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self::with_options(FunnelOptions::with_capacity(capacity))
}
#[must_use]
pub fn with_hasher(hash_builder: DefaultHashBuilder) -> Self {
Self::with_options_and_hasher(FunnelOptions::default(), hash_builder)
}
#[must_use]
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: DefaultHashBuilder) -> Self {
Self::with_options_and_hasher(FunnelOptions::with_capacity(capacity), hash_builder)
}
#[must_use]
pub fn with_options(options: FunnelOptions) -> Self {
Self::with_options_and_hasher(options, DefaultHashBuilder::default())
}
#[must_use]
pub fn with_options_and_hasher(
options: FunnelOptions,
hash_builder: DefaultHashBuilder,
) -> Self {
let reserve_fraction =
sanitize_reserve_fraction(options.reserve_fraction).min(MAX_FUNNEL_RESERVE_FRACTION);
let capacity = options.capacity;
let max_insertions = max_insertions(capacity, reserve_fraction);
let level_count = compute_level_count(reserve_fraction);
let bucket_width = round_up_to_group(compute_bucket_width(reserve_fraction));
let primary_probe_limit = options
.primary_probe_limit
.unwrap_or_else(|| ProbeOps::log_log_probe_limit(capacity))
.max(1);
let mut special_capacity =
choose_special_capacity(capacity, reserve_fraction, bucket_width);
let mut main_capacity = capacity.saturating_sub(special_capacity);
let main_remainder = main_capacity % bucket_width.max(1);
if main_remainder != 0 {
main_capacity = main_capacity.saturating_sub(main_remainder);
special_capacity = capacity.saturating_sub(main_capacity);
}
let total_main_buckets = main_capacity.checked_div(bucket_width).unwrap_or(0);
let level_bucket_counts = partition_funnel_buckets(total_main_buckets, level_count);
let levels = level_bucket_counts
.into_iter()
.enumerate()
.map(|(level_idx, bucket_count)| {
BucketLevel::with_bucket_count(bucket_count, bucket_width, level_salt(level_idx))
})
.collect::<Vec<_>>();
let special = SpecialArray::with_capacity(special_capacity, primary_probe_limit);
Self {
levels,
special,
len: 0,
capacity,
max_insertions,
reserve_fraction,
primary_probe_limit,
max_populated_level: 0,
hash_builder,
}
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn reserve(&mut self, additional: usize) {
let needed = self.len.saturating_add(additional);
if needed <= self.max_insertions {
return;
}
let new_capacity = self.grow_capacity_for(needed).expect("capacity overflow");
self.resize(new_capacity);
}
pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
let needed = self
.len
.checked_add(additional)
.ok_or(TryReserveError::CapacityOverflow)?;
if needed <= self.max_insertions {
return Ok(());
}
let new_capacity = self
.grow_capacity_for(needed)
.ok_or(TryReserveError::CapacityOverflow)?;
self.try_resize(new_capacity)
}
pub fn shrink_to_fit(&mut self) {
self.shrink_to(0);
}
pub fn shrink_to(&mut self, min_capacity: usize) {
if self.len == 0 && min_capacity == 0 {
if self.capacity > 0 {
self.resize(0);
}
return;
}
let lower = self.len.max(min_capacity).max(INITIAL_CAPACITY);
let new_capacity = capacity_for(INITIAL_CAPACITY, lower, self.reserve_fraction)
.expect("capacity overflow");
if new_capacity >= self.capacity {
return;
}
self.resize(new_capacity);
}
fn grow_capacity_for(&self, needed: usize) -> Option<usize> {
capacity_for(
self.capacity.max(INITIAL_CAPACITY),
needed,
self.reserve_fraction,
)
}
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
let key_hash = self.hash_key(&key);
let key_fingerprint = ControlOps::control_fingerprint(key_hash);
let level_candidate =
match self.find_in_levels_with_candidate(&key, key_hash, key_fingerprint) {
Ok(location) => return Some(self.replace_existing_value(location, value)),
Err(level_candidate) => level_candidate,
};
if let Some(location) = level_candidate
&& self.special.primary.len == 0
&& self.special.fallback.len == 0
{
return self.insert_at_location_after_resize_check(
Some(location),
key_hash,
key,
value,
key_fingerprint,
);
}
let (primary_step, primary_candidate) =
self.find_in_special_primary_with_candidate(key_hash, key_fingerprint, &key);
match primary_step {
LookupStep::Found(slot_idx) => {
return Some(
self.replace_existing_value(SlotLocation::SpecialPrimary { slot_idx }, value),
);
}
LookupStep::StopSearch => {
if let Some(location) = level_candidate {
return self.insert_at_location_after_resize_check(
Some(location),
key_hash,
key,
value,
key_fingerprint,
);
}
return self.insert_at_location_after_resize_check(
primary_candidate.map(|slot_idx| SlotLocation::SpecialPrimary { slot_idx }),
key_hash,
key,
value,
key_fingerprint,
);
}
LookupStep::Continue => {}
}
let (fallback_match, fallback_candidate) =
self.find_in_special_fallback_with_candidate(key_hash, key_fingerprint, &key);
if let Some(slot_idx) = fallback_match {
return Some(
self.replace_existing_value(SlotLocation::SpecialFallback { slot_idx }, value),
);
}
let insertion_slot = level_candidate
.or_else(|| primary_candidate.map(|slot_idx| SlotLocation::SpecialPrimary { slot_idx }))
.or_else(|| {
fallback_candidate.map(|slot_idx| SlotLocation::SpecialFallback { slot_idx })
});
self.insert_at_location_after_resize_check(
insertion_slot,
key_hash,
key,
value,
key_fingerprint,
)
}
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let key_hash = self.hash_key(key);
let key_fingerprint = ControlOps::control_fingerprint(key_hash);
match self.find_slot_location_with_hash(key, key_hash, key_fingerprint)? {
SlotLocation::Level {
level_idx,
slot_idx,
} => Some(unsafe { &self.levels[level_idx].table.get_ref(slot_idx).value }),
SlotLocation::SpecialPrimary { slot_idx } => {
Some(unsafe { &self.special.primary.table.get_ref(slot_idx).value })
}
SlotLocation::SpecialFallback { slot_idx } => {
Some(unsafe { &self.special.fallback.table.get_ref(slot_idx).value })
}
}
}
pub fn get_key_value<Q>(&self, key: &Q) -> Option<(&K, &V)>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let key_hash = self.hash_key(key);
let key_fingerprint = ControlOps::control_fingerprint(key_hash);
let entry = match self.find_slot_location_with_hash(key, key_hash, key_fingerprint)? {
SlotLocation::Level {
level_idx,
slot_idx,
} => unsafe { self.levels[level_idx].table.get_ref(slot_idx) },
SlotLocation::SpecialPrimary { slot_idx } => unsafe {
self.special.primary.table.get_ref(slot_idx)
},
SlotLocation::SpecialFallback { slot_idx } => unsafe {
self.special.fallback.table.get_ref(slot_idx)
},
};
Some((&entry.key, &entry.value))
}
pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let key_hash = self.hash_key(key);
let key_fingerprint = ControlOps::control_fingerprint(key_hash);
match self.find_slot_location_with_hash(key, key_hash, key_fingerprint)? {
SlotLocation::Level {
level_idx,
slot_idx,
} => Some(unsafe { &mut self.levels[level_idx].table.get_mut(slot_idx).value }),
SlotLocation::SpecialPrimary { slot_idx } => {
Some(unsafe { &mut self.special.primary.table.get_mut(slot_idx).value })
}
SlotLocation::SpecialFallback { slot_idx } => {
Some(unsafe { &mut self.special.fallback.table.get_mut(slot_idx).value })
}
}
}
pub fn get_disjoint_mut<Q, const N: usize>(&mut self, keys: [&Q; N]) -> Option<[&mut V; N]>
where
K: Borrow<Q> + Eq,
Q: Hash + Eq + ?Sized,
{
let mut locations: [SlotLocation; N] = [SlotLocation::SpecialPrimary { slot_idx: 0 }; N];
for (i, key) in keys.iter().enumerate() {
let key_hash = self.hash_key(*key);
let key_fingerprint = ControlOps::control_fingerprint(key_hash);
locations[i] = self.find_slot_location_with_hash(*key, key_hash, key_fingerprint)?;
}
for i in 0..N {
for j in (i + 1)..N {
assert!(
locations[i] != locations[j],
"get_disjoint_mut: duplicate keys resolve to the same entry",
);
}
}
let levels_ptr: *mut BucketLevel<K, V> = self.levels.as_mut_ptr();
let primary_ptr: *mut SpecialPrimary<K, V> = &raw mut self.special.primary;
let fallback_ptr: *mut SpecialFallback<K, V> = &raw mut self.special.fallback;
let mut out: core::mem::MaybeUninit<[&mut V; N]> = core::mem::MaybeUninit::uninit();
let out_ptr = out.as_mut_ptr().cast::<&mut V>();
for (i, loc) in locations.into_iter().enumerate() {
let value_ref: &mut V = match loc {
SlotLocation::Level {
level_idx,
slot_idx,
} => unsafe {
let level = &mut *levels_ptr.add(level_idx);
&mut level.table.get_mut(slot_idx).value
},
SlotLocation::SpecialPrimary { slot_idx } => unsafe {
&mut (*primary_ptr).table.get_mut(slot_idx).value
},
SlotLocation::SpecialFallback { slot_idx } => unsafe {
&mut (*fallback_ptr).table.get_mut(slot_idx).value
},
};
unsafe { out_ptr.add(i).write(value_ref) };
}
Some(unsafe { out.assume_init() })
}
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let key_hash = self.hash_key(key);
let key_fingerprint = ControlOps::control_fingerprint(key_hash);
self.find_slot_location_with_hash(key, key_hash, key_fingerprint)
.is_some()
}
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.remove_inner(key).map(|(_, v)| v)
}
pub fn remove_entry<Q>(&mut self, key: &Q) -> Option<(K, V)>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.remove_inner(key)
}
fn remove_inner<Q>(&mut self, key: &Q) -> Option<(K, V)>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let key_hash = self.hash_key(key);
let key_fingerprint = ControlOps::control_fingerprint(key_hash);
let location = self.find_slot_location_with_hash(key, key_hash, key_fingerprint)?;
let (removed_entry, needs_resize) = match location {
SlotLocation::Level {
level_idx,
slot_idx,
} => {
let level = &mut self.levels[level_idx];
let removed = unsafe { level.table.take(slot_idx) };
level.table.mark_tombstone(slot_idx);
level.len -= 1;
level.tombstones += 1;
let needs_resize = level.tombstones > level.capacity() / 2;
(removed, needs_resize)
}
SlotLocation::SpecialPrimary { slot_idx } => {
let primary = &mut self.special.primary;
let removed = unsafe { primary.table.take(slot_idx) };
primary.table.mark_tombstone(slot_idx);
primary.len -= 1;
primary.tombstones += 1;
let needs_resize = primary.tombstones > primary.table.capacity() / 2;
(removed, needs_resize)
}
SlotLocation::SpecialFallback { slot_idx } => {
let fallback = &mut self.special.fallback;
let removed = unsafe { fallback.table.take(slot_idx) };
fallback.table.mark_tombstone(slot_idx);
fallback.len -= 1;
fallback.tombstones += 1;
let needs_resize = fallback.tombstones > fallback.capacity() / 2;
(removed, needs_resize)
}
};
self.len -= 1;
self.shrink_max_populated_level();
if needs_resize {
self.resize(self.capacity);
}
Some((removed_entry.key, removed_entry.value))
}
#[must_use]
pub fn keys(&self) -> Keys<'_, K, V> {
Keys::new(self.iter())
}
#[must_use]
pub fn values(&self) -> Values<'_, K, V> {
Values::new(self.iter())
}
#[must_use]
pub fn hasher(&self) -> &DefaultHashBuilder {
&self.hash_builder
}
#[must_use]
pub fn iter(&self) -> FunnelIter<'_, K, V> {
FunnelIter {
levels: &self.levels,
primary: &self.special.primary,
fallback: &self.special.fallback,
phase: FunnelIterPhase::Levels,
level_idx: 0,
slot_idx: 0,
}
}
pub fn iter_mut(&mut self) -> FunnelIterMut<'_, K, V> {
let levels_len = self.levels.len();
let levels = self.levels.as_mut_ptr();
let primary = ptr::from_mut(&mut self.special.primary);
let fallback = ptr::from_mut(&mut self.special.fallback);
FunnelIterMut {
levels,
levels_len,
primary,
fallback,
phase: FunnelIterPhase::Levels,
level_idx: 0,
slot_idx: 0,
_marker: PhantomData,
}
}
pub fn values_mut(&mut self) -> FunnelValuesMut<'_, K, V> {
FunnelValuesMut {
inner: self.iter_mut(),
}
}
#[must_use]
pub fn into_keys(self) -> FunnelIntoKeys<K, V> {
FunnelIntoKeys::new(self.into_iter())
}
#[must_use]
pub fn into_values(self) -> FunnelIntoValues<K, V> {
FunnelIntoValues::new(self.into_iter())
}
pub fn entry(&mut self, key: K) -> Entry<'_, K, V> {
let key_hash = self.hash_key(&key);
let key_fingerprint = ControlOps::control_fingerprint(key_hash);
if let Some(location) = self.find_slot_location_with_hash(&key, key_hash, key_fingerprint) {
Entry::Occupied(OccupiedEntry {
map: self,
location,
})
} else {
Entry::Vacant(VacantEntry {
map: self,
key,
key_hash,
})
}
}
pub fn try_insert(&mut self, key: K, value: V) -> Result<&mut V, OccupiedError<'_, K, V>> {
match self.entry(key) {
Entry::Occupied(entry) => Err(OccupiedError { entry, value }),
Entry::Vacant(entry) => Ok(entry.insert(value)),
}
}
fn insert_for_vacant_entry(&mut self, key: K, value: V, key_hash: u64) -> SlotLocation {
let key_fingerprint = ControlOps::control_fingerprint(key_hash);
let mut location = if self.len < self.max_insertions {
self.choose_slot_for_new_key(key_hash)
} else {
None
};
if location.is_none() {
let new_capacity = if self.capacity == 0 {
INITIAL_CAPACITY
} else {
self.capacity.saturating_mul(2)
};
self.resize(new_capacity);
location = Some(
self.choose_slot_for_new_key(key_hash)
.expect("no free slot found after resize"),
);
}
let final_location = location.expect("location set above");
self.place_new_entry(final_location, key, value, key_fingerprint);
final_location
}
pub fn retain<F>(&mut self, mut f: F)
where
F: FnMut(&K, &mut V) -> bool,
{
self.extract_if(|k, v| !f(k, v)).for_each(drop);
}
pub fn drain(&mut self) -> Drain<'_, K, V> {
Drain {
map: self,
phase: DrainPhase::Levels,
level_idx: 0,
slot_idx: 0,
}
}
pub fn extract_if<F>(&mut self, f: F) -> ExtractIf<'_, K, V, F>
where
F: FnMut(&K, &mut V) -> bool,
{
ExtractIf {
map: self,
pred: f,
phase: DrainPhase::Levels,
level_idx: 0,
slot_idx: 0,
}
}
fn resize_if_needed(&mut self) {
let level_dirty = self
.levels
.iter()
.any(|level| level.tombstones > level.capacity() / 2);
let primary_dirty =
self.special.primary.tombstones > self.special.primary.table.capacity() / 2;
let fallback_dirty =
self.special.fallback.tombstones > self.special.fallback.capacity() / 2;
if level_dirty || primary_dirty || fallback_dirty {
self.resize(self.capacity);
}
}
pub fn clear(&mut self) {
for level in &mut self.levels {
for idx in 0..level.table.capacity() {
if level.table.control_at(idx).is_occupied() {
unsafe { level.table.drop_in_place(idx) };
}
}
level.table.clear_all_controls();
level.len = 0;
level.tombstones = 0;
}
for idx in 0..self.special.primary.table.capacity() {
if self.special.primary.table.control_at(idx).is_occupied() {
unsafe { self.special.primary.table.drop_in_place(idx) };
}
}
self.special.primary.table.clear_all_controls();
self.special.primary.len = 0;
self.special.primary.tombstones = 0;
self.special.primary.group_summaries.fill(0);
for idx in 0..self.special.fallback.table.capacity() {
if self.special.fallback.table.control_at(idx).is_occupied() {
unsafe { self.special.fallback.table.drop_in_place(idx) };
}
}
self.special.fallback.table.clear_all_controls();
self.special.fallback.len = 0;
self.special.fallback.tombstones = 0;
self.len = 0;
self.max_populated_level = 0;
}
fn try_resize(&mut self, new_capacity: usize) -> Result<(), TryReserveError> {
let hash_builder = self.hash_builder.clone();
let mut new_map = Self::try_with_options_and_hasher(
FunnelOptions {
capacity: new_capacity,
reserve_fraction: self.reserve_fraction,
primary_probe_limit: Some(self.primary_probe_limit),
},
hash_builder,
)?;
for level in &mut self.levels {
for idx in 0..level.table.capacity() {
if level.table.control_at(idx).is_occupied() {
let entry = unsafe { level.table.take(idx) };
new_map.insert_new_entry_unchecked(entry.key, entry.value);
}
}
level.table.clear_all_controls();
level.len = 0;
level.tombstones = 0;
}
for idx in 0..self.special.primary.table.capacity() {
if self.special.primary.table.control_at(idx).is_occupied() {
let entry = unsafe { self.special.primary.table.take(idx) };
new_map.insert_new_entry_unchecked(entry.key, entry.value);
}
}
self.special.primary.table.clear_all_controls();
self.special.primary.len = 0;
self.special.primary.tombstones = 0;
self.special.primary.group_summaries.fill(0);
for idx in 0..self.special.fallback.table.capacity() {
if self.special.fallback.table.control_at(idx).is_occupied() {
let entry = unsafe { self.special.fallback.table.take(idx) };
new_map.insert_new_entry_unchecked(entry.key, entry.value);
}
}
self.special.fallback.table.clear_all_controls();
self.special.fallback.len = 0;
self.special.fallback.tombstones = 0;
self.len = 0;
self.max_populated_level = 0;
*self = new_map;
Ok(())
}
fn try_with_options_and_hasher(
options: FunnelOptions,
hash_builder: DefaultHashBuilder,
) -> Result<Self, TryReserveError> {
let reserve_fraction =
sanitize_reserve_fraction(options.reserve_fraction).min(MAX_FUNNEL_RESERVE_FRACTION);
let capacity = options.capacity;
let max_insertions = max_insertions(capacity, reserve_fraction);
let level_count = compute_level_count(reserve_fraction);
let bucket_width = round_up_to_group(compute_bucket_width(reserve_fraction));
let primary_probe_limit = options
.primary_probe_limit
.unwrap_or_else(|| ProbeOps::log_log_probe_limit(capacity))
.max(1);
let mut special_capacity =
choose_special_capacity(capacity, reserve_fraction, bucket_width);
let mut main_capacity = capacity.saturating_sub(special_capacity);
let main_remainder = main_capacity % bucket_width.max(1);
if main_remainder != 0 {
main_capacity = main_capacity.saturating_sub(main_remainder);
special_capacity = capacity.saturating_sub(main_capacity);
}
let total_main_buckets = main_capacity.checked_div(bucket_width).unwrap_or(0);
let level_bucket_counts = partition_funnel_buckets(total_main_buckets, level_count);
let mut levels: Vec<BucketLevel<K, V>> = Vec::new();
levels
.try_reserve_exact(level_bucket_counts.len())
.map_err(|_| TryReserveError::AllocError)?;
for (level_idx, bucket_count) in level_bucket_counts.into_iter().enumerate() {
levels.push(BucketLevel::try_with_bucket_count(
bucket_count,
bucket_width,
level_salt(level_idx),
)?);
}
let special = SpecialArray::try_with_capacity(special_capacity, primary_probe_limit)?;
Ok(Self {
levels,
special,
len: 0,
capacity,
max_insertions,
reserve_fraction,
primary_probe_limit,
max_populated_level: 0,
hash_builder,
})
}
fn resize(&mut self, new_capacity: usize) {
let mut entries = Vec::with_capacity(self.len);
for level in &mut self.levels {
for idx in 0..level.table.capacity() {
if level.table.control_at(idx).is_occupied() {
let entry = unsafe { level.table.take(idx) };
entries.push((entry.key, entry.value));
}
}
level.table.clear_all_controls();
level.len = 0;
level.tombstones = 0;
}
for idx in 0..self.special.primary.table.capacity() {
if self.special.primary.table.control_at(idx).is_occupied() {
let entry = unsafe { self.special.primary.table.take(idx) };
entries.push((entry.key, entry.value));
}
}
self.special.primary.table.clear_all_controls();
self.special.primary.len = 0;
self.special.primary.tombstones = 0;
self.special.primary.group_summaries.fill(0);
for idx in 0..self.special.fallback.table.capacity() {
if self.special.fallback.table.control_at(idx).is_occupied() {
let entry = unsafe { self.special.fallback.table.take(idx) };
entries.push((entry.key, entry.value));
}
}
self.special.fallback.table.clear_all_controls();
self.special.fallback.len = 0;
self.special.fallback.tombstones = 0;
self.len = 0;
self.max_populated_level = 0;
let hash_builder = mem::take(&mut self.hash_builder);
let mut new_map = Self::with_options_and_hasher(
FunnelOptions {
capacity: new_capacity,
reserve_fraction: self.reserve_fraction,
primary_probe_limit: Some(self.primary_probe_limit),
},
hash_builder,
);
for (key, value) in entries {
new_map.insert_new_entry_unchecked(key, value);
}
*self = new_map;
}
#[inline]
fn hash_key<Q>(&self, key: &Q) -> u64
where
Q: Hash + ?Sized,
{
self.hash_builder.hash_one(key)
}
#[inline]
fn special_primary_group_start(&self, key_hash: u64) -> usize {
let mask = self.special.primary.group_count_mask;
if mask == 0 {
return 0;
}
ProbeOps::hash_to_usize(key_hash.rotate_left(11)) & mask
}
#[inline]
fn special_primary_step(&self, key_hash: u64) -> usize {
let mask = self.special.primary.group_count_mask;
(ProbeOps::hash_to_usize(key_hash.rotate_left(43)) | 1) & mask
}
#[inline]
fn special_fallback_bucket_a(key_hash: u64, bucket_count: usize) -> usize {
ProbeOps::hash_to_usize(key_hash.rotate_left(19)) % bucket_count
}
#[inline]
fn special_fallback_bucket_b(key_hash: u64, bucket_count: usize) -> usize {
ProbeOps::hash_to_usize(key_hash.rotate_left(37)) % bucket_count
}
#[inline]
fn choose_slot_for_new_key(&self, key_hash: u64) -> Option<SlotLocation> {
for (level_idx, level) in self.levels.iter().enumerate() {
if let Some(slot_idx) = Self::first_free_in_level_bucket(key_hash, level) {
return Some(SlotLocation::Level {
level_idx,
slot_idx,
});
}
}
if let Some(slot_idx) = self.first_free_in_special_primary(key_hash) {
return Some(SlotLocation::SpecialPrimary { slot_idx });
}
self.first_free_in_special_fallback(key_hash)
.map(|slot_idx| SlotLocation::SpecialFallback { slot_idx })
}
fn find_in_levels_with_candidate<Q>(
&self,
key: &Q,
key_hash: u64,
key_fingerprint: u8,
) -> Result<SlotLocation, Option<SlotLocation>>
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
let search_limit = (self.max_populated_level + 1).min(self.levels.len());
let mut candidate = None;
for (level_idx, level) in self.levels[..search_limit].iter().enumerate() {
let (lookup_step, slot_candidate) =
Self::find_in_level_bucket_with_candidate(key_hash, key_fingerprint, key, level);
if candidate.is_none() {
candidate = slot_candidate.map(|slot_idx| SlotLocation::Level {
level_idx,
slot_idx,
});
}
match lookup_step {
LookupStep::Found(slot_idx) => {
return Ok(SlotLocation::Level {
level_idx,
slot_idx,
});
}
LookupStep::Continue => {}
LookupStep::StopSearch => return Err(candidate),
}
}
Err(candidate)
}
#[inline]
fn replace_existing_value(&mut self, location: SlotLocation, value: V) -> V {
match location {
SlotLocation::Level {
level_idx,
slot_idx,
} => {
let entry = unsafe { self.levels[level_idx].table.get_mut(slot_idx) };
mem::replace(&mut entry.value, value)
}
SlotLocation::SpecialPrimary { slot_idx } => {
let entry = unsafe { self.special.primary.table.get_mut(slot_idx) };
mem::replace(&mut entry.value, value)
}
SlotLocation::SpecialFallback { slot_idx } => {
let entry = unsafe { self.special.fallback.table.get_mut(slot_idx) };
mem::replace(&mut entry.value, value)
}
}
}
#[inline]
fn insert_new_entry_unchecked(&mut self, key: K, value: V) {
let key_hash = self.hash_key(&key);
let key_fingerprint = ControlOps::control_fingerprint(key_hash);
let location = self
.choose_slot_for_new_key(key_hash)
.expect("resized funnel map should have free slot");
self.place_new_entry(location, key, value, key_fingerprint);
}
#[inline]
fn insert_at_location_after_resize_check(
&mut self,
location: Option<SlotLocation>,
key_hash: u64,
key: K,
value: V,
key_fingerprint: u8,
) -> Option<V> {
let final_location = if self.len >= self.max_insertions || location.is_none() {
let new_capacity = if self.capacity == 0 {
INITIAL_CAPACITY
} else {
self.capacity.saturating_mul(2)
};
self.resize(new_capacity);
self.choose_slot_for_new_key(key_hash)
.expect("no free slot found after resize")
} else {
match location {
Some(location) => location,
None => unreachable!("checked for resize above"),
}
};
self.place_new_entry(final_location, key, value, key_fingerprint);
None
}
#[inline]
fn place_new_entry(&mut self, location: SlotLocation, key: K, value: V, key_fingerprint: u8) {
match location {
SlotLocation::Level {
level_idx,
slot_idx,
} => {
let level = &mut self.levels[level_idx];
level
.table
.write_with_control(slot_idx, SlotEntry { key, value }, key_fingerprint);
level.len += 1;
if level_idx > self.max_populated_level {
self.max_populated_level = level_idx;
}
}
SlotLocation::SpecialPrimary { slot_idx } => {
let group_idx = slot_idx / GROUP_SIZE;
let primary = &mut self.special.primary;
let was_tombstone = primary.table.control_at(slot_idx) == CTRL_TOMBSTONE;
primary.table.write_with_control(
slot_idx,
SlotEntry { key, value },
key_fingerprint,
);
primary.len += 1;
primary.group_summaries[group_idx] |= ControlOps::fingerprint_bit(key_fingerprint);
if was_tombstone {
primary.tombstones -= 1;
}
}
SlotLocation::SpecialFallback { slot_idx } => {
let fallback = &mut self.special.fallback;
fallback.table.write_with_control(
slot_idx,
SlotEntry { key, value },
key_fingerprint,
);
fallback.len += 1;
}
}
self.len += 1;
}
fn first_free_in_level_bucket(key_hash: u64, level: &BucketLevel<K, V>) -> Option<usize> {
if level.len >= level.capacity() || level.bucket_count == 0 {
return None;
}
let bucket_idx = level.bucket_index(key_hash);
let bucket_range = level.bucket_range(bucket_idx);
debug_assert_eq!(bucket_range.start % GROUP_SIZE, 0);
let group_idx = bucket_range.start / GROUP_SIZE;
level
.table
.group_free_mask(group_idx)
.truncate_to(level.bucket_size)
.lowest()
.map(|offset| bucket_range.start + offset)
}
fn first_free_in_special_primary(&self, key_hash: u64) -> Option<usize> {
let primary = &self.special.primary;
if primary.len >= primary.table.capacity() {
return None;
}
let group_count = primary.table.group_count();
let group_limit = self.primary_probe_limit.min(group_count.max(1));
let mask = primary.group_count_mask;
let mut group_idx = self.special_primary_group_start(key_hash);
let step = self.special_primary_step(key_hash);
for _ in 0..group_limit {
if let Some(slot_idx) = Self::first_free_in_special_primary_group(primary, group_idx) {
return Some(slot_idx);
}
group_idx = (group_idx + step) & mask;
}
None
}
fn first_free_in_special_fallback(&self, key_hash: u64) -> Option<usize> {
let fallback = &self.special.fallback;
if fallback.len >= fallback.capacity() {
return None;
}
let bucket_count = fallback.bucket_count();
if bucket_count == 0 {
return None;
}
let bucket_a = Self::special_fallback_bucket_a(key_hash, bucket_count);
let bucket_b = Self::special_fallback_bucket_b(key_hash, bucket_count);
for &bucket_idx in &[bucket_a, bucket_b] {
let range = fallback.bucket_range(bucket_idx);
for slot_idx in range {
if fallback.table.control_at(slot_idx).is_free() {
return Some(slot_idx);
}
}
}
None
}
#[inline]
fn first_free_in_special_primary_group(
primary: &SpecialPrimary<K, V>,
group_idx: usize,
) -> Option<usize> {
primary.table.first_free_in_group(group_idx)
}
fn find_in_level_bucket<Q>(
key_hash: u64,
key_fingerprint: u8,
key: &Q,
level: &BucketLevel<K, V>,
) -> LookupStep
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
if level.len == 0 || level.bucket_count == 0 {
return LookupStep::Continue;
}
let bucket_idx = level.bucket_index(key_hash);
let bucket_range = level.bucket_range(bucket_idx);
debug_assert_eq!(bucket_range.start % GROUP_SIZE, 0);
let group_idx = bucket_range.start / GROUP_SIZE;
for relative_idx in level
.table
.group_match_mask(group_idx, key_fingerprint)
.truncate_to(level.bucket_size)
{
let slot_idx = bucket_range.start + relative_idx;
let entry = unsafe { level.table.get_ref(slot_idx) };
if entry.key.borrow() == key {
return LookupStep::Found(slot_idx);
}
}
if level
.table
.group_match_mask(group_idx, CTRL_EMPTY)
.truncate_to(level.bucket_size)
.any()
{
return LookupStep::StopSearch;
}
LookupStep::Continue
}
fn find_in_level_bucket_with_candidate<Q>(
key_hash: u64,
key_fingerprint: u8,
key: &Q,
level: &BucketLevel<K, V>,
) -> (LookupStep, Option<usize>)
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
if level.len == 0 {
return (LookupStep::Continue, None);
}
if level.bucket_count == 0 {
return (LookupStep::Continue, None);
}
let bucket_idx = level.bucket_index(key_hash);
let bucket_range = level.bucket_range(bucket_idx);
debug_assert_eq!(bucket_range.start % GROUP_SIZE, 0);
let group_idx = bucket_range.start / GROUP_SIZE;
for relative_idx in level
.table
.group_match_mask(group_idx, key_fingerprint)
.truncate_to(level.bucket_size)
{
let slot_idx = bucket_range.start + relative_idx;
let entry = unsafe { level.table.get_ref(slot_idx) };
if entry.key.borrow() == key {
let free_candidate = level
.table
.group_free_mask(group_idx)
.truncate_to(level.bucket_size)
.lowest()
.map(|o| bucket_range.start + o);
return (LookupStep::Found(slot_idx), free_candidate);
}
}
let free_candidate = level
.table
.group_free_mask(group_idx)
.truncate_to(level.bucket_size)
.lowest()
.map(|o| bucket_range.start + o);
let step = if level
.table
.group_match_mask(group_idx, CTRL_EMPTY)
.truncate_to(level.bucket_size)
.any()
{
LookupStep::StopSearch
} else {
LookupStep::Continue
};
(step, free_candidate)
}
fn find_in_special_primary<Q>(&self, key_hash: u64, key_fingerprint: u8, key: &Q) -> LookupStep
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
let primary = &self.special.primary;
if primary.len == 0 {
return LookupStep::Continue;
}
let fingerprint_mask = ControlOps::fingerprint_bit(key_fingerprint);
let group_count = primary.table.group_count();
let group_limit = self.primary_probe_limit.min(group_count.max(1));
let mask = primary.group_count_mask;
let mut group_idx = self.special_primary_group_start(key_hash);
let step = self.special_primary_step(key_hash);
for _ in 0..group_limit {
if primary.group_summaries[group_idx] & fingerprint_mask != 0 {
for relative_idx in primary.table.group_match_mask(group_idx, key_fingerprint) {
let slot_idx = group_idx * GROUP_SIZE + relative_idx;
let entry = unsafe { primary.table.get_ref(slot_idx) };
if entry.key.borrow() == key {
return LookupStep::Found(slot_idx);
}
}
}
if !primary
.table
.group_match_mask(group_idx, CTRL_TOMBSTONE)
.any()
&& primary.table.group_match_mask(group_idx, CTRL_EMPTY).any()
{
return LookupStep::StopSearch;
}
let next = (group_idx + step) & mask;
unsafe { prefetch_read(primary.table.group_data_ptr(next)) };
group_idx = next;
}
LookupStep::Continue
}
fn find_in_special_primary_with_candidate<Q>(
&self,
key_hash: u64,
key_fingerprint: u8,
key: &Q,
) -> (LookupStep, Option<usize>)
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
let primary = &self.special.primary;
if primary.table.capacity() == 0 {
return (LookupStep::Continue, None);
}
if primary.len == 0 {
return (
LookupStep::Continue,
self.first_free_in_special_primary(key_hash),
);
}
let fingerprint_mask = ControlOps::fingerprint_bit(key_fingerprint);
let group_count = primary.table.group_count();
let group_limit = self.primary_probe_limit.min(group_count.max(1));
let mask = primary.group_count_mask;
let mut candidate = None;
let mut group_idx = self.special_primary_group_start(key_hash);
let step = self.special_primary_step(key_hash);
for _ in 0..group_limit {
let free_in_group = if candidate.is_none() {
let slot = primary.table.first_free_in_group(group_idx);
if let Some(slot) = slot {
candidate = Some(slot);
}
slot.is_some()
} else {
primary.table.group_match_mask(group_idx, CTRL_EMPTY).any()
};
if primary.group_summaries[group_idx] & fingerprint_mask != 0 {
for relative_idx in primary.table.group_match_mask(group_idx, key_fingerprint) {
let slot_idx = group_idx * GROUP_SIZE + relative_idx;
let entry = unsafe { primary.table.get_ref(slot_idx) };
if entry.key.borrow() == key {
return (LookupStep::Found(slot_idx), candidate);
}
}
}
if free_in_group
&& !primary
.table
.group_match_mask(group_idx, CTRL_TOMBSTONE)
.any()
{
return (LookupStep::StopSearch, candidate);
}
let next = (group_idx + step) & mask;
unsafe { prefetch_read(primary.table.group_data_ptr(next)) };
group_idx = next;
}
(LookupStep::Continue, candidate)
}
fn find_in_special_fallback<Q>(
&self,
key_hash: u64,
key_fingerprint: u8,
key: &Q,
) -> Option<usize>
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
let fallback = &self.special.fallback;
if fallback.len == 0 {
return None;
}
let bucket_count = fallback.bucket_count();
if bucket_count == 0 {
return None;
}
let bucket_a = Self::special_fallback_bucket_a(key_hash, bucket_count);
let bucket_b = Self::special_fallback_bucket_b(key_hash, bucket_count);
for bucket_idx in [bucket_a, bucket_b] {
let range = fallback.bucket_range(bucket_idx);
let controls = unsafe {
std::slice::from_raw_parts(
fallback.table.group_data_ptr(0).add(range.start),
range.len(),
)
};
let mut match_offset = 0;
while let Some(relative_idx) = ControlOps::find_next_fingerprint_in_controls(
controls,
key_fingerprint,
match_offset,
) {
let slot_idx = range.start + relative_idx;
let entry = unsafe { fallback.table.get_ref(slot_idx) };
if entry.key.borrow() == key {
return Some(slot_idx);
}
match_offset = relative_idx + 1;
}
}
None
}
fn find_in_special_fallback_with_candidate<Q>(
&self,
key_hash: u64,
key_fingerprint: u8,
key: &Q,
) -> (Option<usize>, Option<usize>)
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
let fallback = &self.special.fallback;
if fallback.capacity() == 0 {
return (None, None);
}
if fallback.len == 0 {
return (None, self.first_free_in_special_fallback(key_hash));
}
let bucket_count = fallback.bucket_count();
if bucket_count == 0 {
return (None, None);
}
let bucket_a = Self::special_fallback_bucket_a(key_hash, bucket_count);
let bucket_b = Self::special_fallback_bucket_b(key_hash, bucket_count);
let mut candidate = None;
for &bucket_idx in &[bucket_a, bucket_b] {
if candidate.is_some() {
break;
}
let range = fallback.bucket_range(bucket_idx);
for slot_idx in range {
if fallback.table.control_at(slot_idx).is_free() {
candidate = Some(slot_idx);
break;
}
}
}
(
self.find_in_special_fallback(key_hash, key_fingerprint, key),
candidate,
)
}
fn find_slot_location_with_hash<Q>(
&self,
key: &Q,
key_hash: u64,
key_fingerprint: u8,
) -> Option<SlotLocation>
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
let search_limit = (self.max_populated_level + 1).min(self.levels.len());
for (level_idx, level) in self.levels[..search_limit].iter().enumerate() {
match Self::find_in_level_bucket(key_hash, key_fingerprint, key, level) {
LookupStep::Found(slot_idx) => {
return Some(SlotLocation::Level {
level_idx,
slot_idx,
});
}
LookupStep::Continue => {}
LookupStep::StopSearch => return None,
}
}
if self.special.primary.len == 0 && self.special.fallback.len == 0 {
return None;
}
match self.find_in_special_primary(key_hash, key_fingerprint, key) {
LookupStep::Found(slot_idx) => return Some(SlotLocation::SpecialPrimary { slot_idx }),
LookupStep::Continue => {}
LookupStep::StopSearch => return None,
}
self.find_in_special_fallback(key_hash, key_fingerprint, key)
.map(|slot_idx| SlotLocation::SpecialFallback { slot_idx })
}
fn shrink_max_populated_level(&mut self) {
while self.max_populated_level > 0 && self.levels[self.max_populated_level].len == 0 {
self.max_populated_level -= 1;
}
}
}
pub enum Entry<'a, K: 'a, V: 'a> {
Occupied(OccupiedEntry<'a, K, V>),
Vacant(VacantEntry<'a, K, V>),
}
pub struct OccupiedEntry<'a, K, V> {
map: &'a mut FunnelHashMap<K, V>,
location: SlotLocation,
}
impl<'a, K, V> OccupiedEntry<'a, K, V>
where
K: Eq + Hash,
{
#[must_use]
pub fn key(&self) -> &K {
match self.location {
SlotLocation::Level {
level_idx,
slot_idx,
} => unsafe { &self.map.levels[level_idx].table.get_ref(slot_idx).key },
SlotLocation::SpecialPrimary { slot_idx } => unsafe {
&self.map.special.primary.table.get_ref(slot_idx).key
},
SlotLocation::SpecialFallback { slot_idx } => unsafe {
&self.map.special.fallback.table.get_ref(slot_idx).key
},
}
}
#[must_use]
pub fn get(&self) -> &V {
match self.location {
SlotLocation::Level {
level_idx,
slot_idx,
} => unsafe { &self.map.levels[level_idx].table.get_ref(slot_idx).value },
SlotLocation::SpecialPrimary { slot_idx } => unsafe {
&self.map.special.primary.table.get_ref(slot_idx).value
},
SlotLocation::SpecialFallback { slot_idx } => unsafe {
&self.map.special.fallback.table.get_ref(slot_idx).value
},
}
}
pub fn get_mut(&mut self) -> &mut V {
match self.location {
SlotLocation::Level {
level_idx,
slot_idx,
} => unsafe { &mut self.map.levels[level_idx].table.get_mut(slot_idx).value },
SlotLocation::SpecialPrimary { slot_idx } => unsafe {
&mut self.map.special.primary.table.get_mut(slot_idx).value
},
SlotLocation::SpecialFallback { slot_idx } => unsafe {
&mut self.map.special.fallback.table.get_mut(slot_idx).value
},
}
}
#[must_use]
pub fn into_mut(self) -> &'a mut V {
match self.location {
SlotLocation::Level {
level_idx,
slot_idx,
} => unsafe { &mut self.map.levels[level_idx].table.get_mut(slot_idx).value },
SlotLocation::SpecialPrimary { slot_idx } => unsafe {
&mut self.map.special.primary.table.get_mut(slot_idx).value
},
SlotLocation::SpecialFallback { slot_idx } => unsafe {
&mut self.map.special.fallback.table.get_mut(slot_idx).value
},
}
}
pub fn insert(&mut self, value: V) -> V {
self.map.replace_existing_value(self.location, value)
}
#[must_use]
pub fn remove(self) -> V {
self.remove_entry().1
}
#[must_use]
pub fn remove_entry(self) -> (K, V) {
let (removed_entry, needs_resize) = match self.location {
SlotLocation::Level {
level_idx,
slot_idx,
} => {
let level = &mut self.map.levels[level_idx];
let removed = unsafe { level.table.take(slot_idx) };
level.table.mark_tombstone(slot_idx);
level.len -= 1;
level.tombstones += 1;
let needs_resize = level.tombstones > level.capacity() / 2;
(removed, needs_resize)
}
SlotLocation::SpecialPrimary { slot_idx } => {
let primary = &mut self.map.special.primary;
let removed = unsafe { primary.table.take(slot_idx) };
primary.table.mark_tombstone(slot_idx);
primary.len -= 1;
primary.tombstones += 1;
let needs_resize = primary.tombstones > primary.table.capacity() / 2;
(removed, needs_resize)
}
SlotLocation::SpecialFallback { slot_idx } => {
let fallback = &mut self.map.special.fallback;
let removed = unsafe { fallback.table.take(slot_idx) };
fallback.table.mark_tombstone(slot_idx);
fallback.len -= 1;
fallback.tombstones += 1;
let needs_resize = fallback.tombstones > fallback.capacity() / 2;
(removed, needs_resize)
}
};
self.map.len -= 1;
self.map.shrink_max_populated_level();
if needs_resize {
let capacity = self.map.capacity;
self.map.resize(capacity);
}
(removed_entry.key, removed_entry.value)
}
}
pub struct VacantEntry<'a, K, V> {
map: &'a mut FunnelHashMap<K, V>,
key: K,
key_hash: u64,
}
impl<'a, K, V> VacantEntry<'a, K, V>
where
K: Eq + Hash,
{
pub fn key(&self) -> &K {
&self.key
}
#[must_use]
pub fn into_key(self) -> K {
self.key
}
pub fn insert(self, value: V) -> &'a mut V {
let location = self
.map
.insert_for_vacant_entry(self.key, value, self.key_hash);
match location {
SlotLocation::Level {
level_idx,
slot_idx,
} => unsafe { &mut self.map.levels[level_idx].table.get_mut(slot_idx).value },
SlotLocation::SpecialPrimary { slot_idx } => unsafe {
&mut self.map.special.primary.table.get_mut(slot_idx).value
},
SlotLocation::SpecialFallback { slot_idx } => unsafe {
&mut self.map.special.fallback.table.get_mut(slot_idx).value
},
}
}
}
impl<'a, K, V> Entry<'a, K, V>
where
K: Eq + Hash,
{
pub fn or_insert(self, default: V) -> &'a mut V {
match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(default),
}
}
pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(default()),
}
}
pub fn or_insert_with_key<F: FnOnce(&K) -> V>(self, default: F) -> &'a mut V {
match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let value = default(entry.key());
entry.insert(value)
}
}
}
pub fn key(&self) -> &K {
match self {
Entry::Occupied(entry) => entry.key(),
Entry::Vacant(entry) => entry.key(),
}
}
#[must_use]
pub fn and_modify<F: FnOnce(&mut V)>(self, f: F) -> Self {
match self {
Entry::Occupied(mut entry) => {
f(entry.get_mut());
Entry::Occupied(entry)
}
Entry::Vacant(entry) => Entry::Vacant(entry),
}
}
}
impl<'a, K, V> Entry<'a, K, V>
where
K: Eq + Hash,
V: Default,
{
pub fn or_default(self) -> &'a mut V {
match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(V::default()),
}
}
}
pub type OccupiedError<'a, K, V> = CommonOccupiedError<OccupiedEntry<'a, K, V>, V>;
impl<K, V> EntryView for OccupiedEntry<'_, K, V>
where
K: Eq + Hash,
{
type Key = K;
type Value = V;
fn view_key(&self) -> &K {
self.key()
}
fn view_value(&self) -> &V {
self.get()
}
}
#[derive(Debug, Clone, Copy)]
enum FunnelIterPhase {
Levels,
Primary,
Fallback,
Done,
}
#[derive(Clone)]
pub struct FunnelIter<'a, K, V> {
levels: &'a [BucketLevel<K, V>],
primary: &'a SpecialPrimary<K, V>,
fallback: &'a SpecialFallback<K, V>,
phase: FunnelIterPhase,
level_idx: usize,
slot_idx: usize,
}
impl<K, V> fmt::Debug for FunnelIter<'_, K, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FunnelIter")
.field("phase", &self.phase)
.field("level_idx", &self.level_idx)
.field("slot_idx", &self.slot_idx)
.finish_non_exhaustive()
}
}
impl<'a, K, V> Iterator for FunnelIter<'a, K, V> {
type Item = (&'a K, &'a V);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.phase {
FunnelIterPhase::Levels => {
while self.level_idx < self.levels.len() {
let table = &self.levels[self.level_idx].table;
while self.slot_idx < table.capacity() {
let idx = self.slot_idx;
self.slot_idx += 1;
if table.control_at(idx).is_occupied() {
let entry = unsafe { table.get_ref(idx) };
return Some((&entry.key, &entry.value));
}
}
self.level_idx += 1;
self.slot_idx = 0;
}
self.phase = FunnelIterPhase::Primary;
self.slot_idx = 0;
}
FunnelIterPhase::Primary => {
let table = &self.primary.table;
while self.slot_idx < table.capacity() {
let idx = self.slot_idx;
self.slot_idx += 1;
if table.control_at(idx).is_occupied() {
let entry = unsafe { table.get_ref(idx) };
return Some((&entry.key, &entry.value));
}
}
self.phase = FunnelIterPhase::Fallback;
self.slot_idx = 0;
}
FunnelIterPhase::Fallback => {
let table = &self.fallback.table;
while self.slot_idx < table.capacity() {
let idx = self.slot_idx;
self.slot_idx += 1;
if table.control_at(idx).is_occupied() {
let entry = unsafe { table.get_ref(idx) };
return Some((&entry.key, &entry.value));
}
}
self.phase = FunnelIterPhase::Done;
}
FunnelIterPhase::Done => return None,
}
}
}
}
impl<K, V> FusedIterator for FunnelIter<'_, K, V> {}
impl<'a, K, V> IntoIterator for &'a FunnelHashMap<K, V>
where
K: Eq + Hash,
{
type Item = (&'a K, &'a V);
type IntoIter = FunnelIter<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DrainPhase {
Levels,
Primary,
Fallback,
Done,
}
pub struct Drain<'a, K, V> {
map: &'a mut FunnelHashMap<K, V>,
phase: DrainPhase,
level_idx: usize,
slot_idx: usize,
}
impl<K: fmt::Debug, V: fmt::Debug> fmt::Debug for Drain<'_, K, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Drain").finish_non_exhaustive()
}
}
impl<K, V> Iterator for Drain<'_, K, V> {
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.phase {
DrainPhase::Levels => {
while self.level_idx < self.map.levels.len() {
let level = &mut self.map.levels[self.level_idx];
while self.slot_idx < level.table.capacity() {
let idx = self.slot_idx;
self.slot_idx += 1;
if level.table.control_at(idx).is_occupied() {
let entry = unsafe { level.table.take(idx) };
level.table.mark_tombstone(idx);
level.len -= 1;
level.tombstones += 1;
self.map.len -= 1;
return Some((entry.key, entry.value));
}
}
self.level_idx += 1;
self.slot_idx = 0;
}
self.phase = DrainPhase::Primary;
self.slot_idx = 0;
}
DrainPhase::Primary => {
let primary = &mut self.map.special.primary;
while self.slot_idx < primary.table.capacity() {
let idx = self.slot_idx;
self.slot_idx += 1;
if primary.table.control_at(idx).is_occupied() {
let entry = unsafe { primary.table.take(idx) };
primary.table.mark_tombstone(idx);
primary.len -= 1;
primary.tombstones += 1;
self.map.len -= 1;
return Some((entry.key, entry.value));
}
}
self.phase = DrainPhase::Fallback;
self.slot_idx = 0;
}
DrainPhase::Fallback => {
let fallback = &mut self.map.special.fallback;
while self.slot_idx < fallback.table.capacity() {
let idx = self.slot_idx;
self.slot_idx += 1;
if fallback.table.control_at(idx).is_occupied() {
let entry = unsafe { fallback.table.take(idx) };
fallback.table.mark_tombstone(idx);
fallback.len -= 1;
fallback.tombstones += 1;
self.map.len -= 1;
return Some((entry.key, entry.value));
}
}
self.phase = DrainPhase::Done;
}
DrainPhase::Done => return None,
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.map.len, Some(self.map.len))
}
}
impl<K, V> ExactSizeIterator for Drain<'_, K, V> {}
impl<K, V> FusedIterator for Drain<'_, K, V> {}
impl<K, V> Drop for Drain<'_, K, V> {
fn drop(&mut self) {
for _ in &mut *self {}
for level in &mut self.map.levels {
level.table.clear_all_controls();
level.len = 0;
level.tombstones = 0;
}
self.map.special.primary.table.clear_all_controls();
self.map.special.primary.len = 0;
self.map.special.primary.tombstones = 0;
self.map.special.primary.group_summaries.fill(0);
self.map.special.fallback.table.clear_all_controls();
self.map.special.fallback.len = 0;
self.map.special.fallback.tombstones = 0;
self.map.len = 0;
self.map.max_populated_level = 0;
}
}
pub struct ExtractIf<'a, K, V, F>
where
K: Eq + Hash,
F: FnMut(&K, &mut V) -> bool,
{
map: &'a mut FunnelHashMap<K, V>,
pred: F,
phase: DrainPhase,
level_idx: usize,
slot_idx: usize,
}
impl<K, V, F> fmt::Debug for ExtractIf<'_, K, V, F>
where
K: Eq + Hash,
F: FnMut(&K, &mut V) -> bool,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ExtractIf").finish_non_exhaustive()
}
}
impl<K, V, F> Iterator for ExtractIf<'_, K, V, F>
where
K: Eq + Hash,
F: FnMut(&K, &mut V) -> bool,
{
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.phase {
DrainPhase::Levels => {
while self.level_idx < self.map.levels.len() {
let level = &mut self.map.levels[self.level_idx];
while self.slot_idx < level.table.capacity() {
let idx = self.slot_idx;
self.slot_idx += 1;
if !level.table.control_at(idx).is_occupied() {
continue;
}
let entry = unsafe { level.table.get_mut(idx) };
if (self.pred)(&entry.key, &mut entry.value) {
let removed = unsafe { level.table.take(idx) };
level.table.mark_tombstone(idx);
level.len -= 1;
level.tombstones += 1;
self.map.len -= 1;
return Some((removed.key, removed.value));
}
}
self.level_idx += 1;
self.slot_idx = 0;
}
self.phase = DrainPhase::Primary;
self.slot_idx = 0;
}
DrainPhase::Primary => {
let primary = &mut self.map.special.primary;
while self.slot_idx < primary.table.capacity() {
let idx = self.slot_idx;
self.slot_idx += 1;
if !primary.table.control_at(idx).is_occupied() {
continue;
}
let entry = unsafe { primary.table.get_mut(idx) };
if (self.pred)(&entry.key, &mut entry.value) {
let removed = unsafe { primary.table.take(idx) };
primary.table.mark_tombstone(idx);
primary.len -= 1;
primary.tombstones += 1;
self.map.len -= 1;
return Some((removed.key, removed.value));
}
}
self.phase = DrainPhase::Fallback;
self.slot_idx = 0;
}
DrainPhase::Fallback => {
let fallback = &mut self.map.special.fallback;
while self.slot_idx < fallback.table.capacity() {
let idx = self.slot_idx;
self.slot_idx += 1;
if !fallback.table.control_at(idx).is_occupied() {
continue;
}
let entry = unsafe { fallback.table.get_mut(idx) };
if (self.pred)(&entry.key, &mut entry.value) {
let removed = unsafe { fallback.table.take(idx) };
fallback.table.mark_tombstone(idx);
fallback.len -= 1;
fallback.tombstones += 1;
self.map.len -= 1;
return Some((removed.key, removed.value));
}
}
self.phase = DrainPhase::Done;
}
DrainPhase::Done => return None,
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(self.map.len))
}
}
impl<K, V, F> Drop for ExtractIf<'_, K, V, F>
where
K: Eq + Hash,
F: FnMut(&K, &mut V) -> bool,
{
fn drop(&mut self) {
self.map.resize_if_needed();
}
}
pub type Keys<'a, K, V> = CommonKeys<FunnelIter<'a, K, V>>;
pub type Values<'a, K, V> = CommonValues<FunnelIter<'a, K, V>>;
pub struct FunnelIterMut<'a, K, V> {
levels: *mut BucketLevel<K, V>,
levels_len: usize,
primary: *mut SpecialPrimary<K, V>,
fallback: *mut SpecialFallback<K, V>,
phase: FunnelIterPhase,
level_idx: usize,
slot_idx: usize,
_marker: PhantomData<&'a mut FunnelHashMap<K, V>>,
}
unsafe impl<K: Send, V: Send> Send for FunnelIterMut<'_, K, V> {}
unsafe impl<K: Sync, V: Sync> Sync for FunnelIterMut<'_, K, V> {}
impl<'a, K, V> Iterator for FunnelIterMut<'a, K, V> {
type Item = (&'a K, &'a mut V);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.phase {
FunnelIterPhase::Levels => {
while self.level_idx < self.levels_len {
let level = unsafe { &mut *self.levels.add(self.level_idx) };
let cap = level.table.capacity();
while self.slot_idx < cap {
let idx = self.slot_idx;
self.slot_idx += 1;
if level.table.control_at(idx).is_occupied() {
let entry = unsafe { level.table.get_mut(idx) };
let key: &'a K = unsafe { &*ptr::from_ref(&entry.key) };
let val: &'a mut V =
unsafe { &mut *ptr::from_mut(&mut entry.value) };
return Some((key, val));
}
}
self.level_idx += 1;
self.slot_idx = 0;
}
self.phase = FunnelIterPhase::Primary;
self.slot_idx = 0;
}
FunnelIterPhase::Primary => {
let primary = unsafe { &mut *self.primary };
let cap = primary.table.capacity();
while self.slot_idx < cap {
let idx = self.slot_idx;
self.slot_idx += 1;
if primary.table.control_at(idx).is_occupied() {
let entry = unsafe { primary.table.get_mut(idx) };
let key: &'a K = unsafe { &*ptr::from_ref(&entry.key) };
let val: &'a mut V = unsafe { &mut *ptr::from_mut(&mut entry.value) };
return Some((key, val));
}
}
self.phase = FunnelIterPhase::Fallback;
self.slot_idx = 0;
}
FunnelIterPhase::Fallback => {
let fallback = unsafe { &mut *self.fallback };
let cap = fallback.table.capacity();
while self.slot_idx < cap {
let idx = self.slot_idx;
self.slot_idx += 1;
if fallback.table.control_at(idx).is_occupied() {
let entry = unsafe { fallback.table.get_mut(idx) };
let key: &'a K = unsafe { &*ptr::from_ref(&entry.key) };
let val: &'a mut V = unsafe { &mut *ptr::from_mut(&mut entry.value) };
return Some((key, val));
}
}
self.phase = FunnelIterPhase::Done;
}
FunnelIterPhase::Done => return None,
}
}
}
}
impl<K, V> fmt::Debug for FunnelIterMut<'_, K, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FunnelIterMut")
.field("level_idx", &self.level_idx)
.field("slot_idx", &self.slot_idx)
.finish_non_exhaustive()
}
}
impl<'a, K, V> IntoIterator for &'a mut FunnelHashMap<K, V>
where
K: Eq + Hash,
{
type Item = (&'a K, &'a mut V);
type IntoIter = FunnelIterMut<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
pub struct FunnelValuesMut<'a, K, V> {
inner: FunnelIterMut<'a, K, V>,
}
impl<'a, K, V> Iterator for FunnelValuesMut<'a, K, V> {
type Item = &'a mut V;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(_, v)| v)
}
}
impl<K, V> fmt::Debug for FunnelValuesMut<'_, K, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FunnelValuesMut")
.field("level_idx", &self.inner.level_idx)
.field("slot_idx", &self.inner.slot_idx)
.finish_non_exhaustive()
}
}
pub struct FunnelIntoIter<K, V> {
map: FunnelHashMap<K, V>,
phase: FunnelIterPhase,
level_idx: usize,
slot_idx: usize,
}
impl<K, V> Iterator for FunnelIntoIter<K, V> {
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.phase {
FunnelIterPhase::Levels => {
while self.level_idx < self.map.levels.len() {
let level = &mut self.map.levels[self.level_idx];
let cap = level.table.capacity();
while self.slot_idx < cap {
let idx = self.slot_idx;
self.slot_idx += 1;
if level.table.control_at(idx).is_occupied() {
let entry = unsafe { level.table.take(idx) };
level.table.mark_tombstone(idx);
return Some((entry.key, entry.value));
}
}
self.level_idx += 1;
self.slot_idx = 0;
}
self.phase = FunnelIterPhase::Primary;
self.slot_idx = 0;
}
FunnelIterPhase::Primary => {
let table = &mut self.map.special.primary.table;
let cap = table.capacity();
while self.slot_idx < cap {
let idx = self.slot_idx;
self.slot_idx += 1;
if table.control_at(idx).is_occupied() {
let entry = unsafe { table.take(idx) };
table.mark_tombstone(idx);
return Some((entry.key, entry.value));
}
}
self.phase = FunnelIterPhase::Fallback;
self.slot_idx = 0;
}
FunnelIterPhase::Fallback => {
let table = &mut self.map.special.fallback.table;
let cap = table.capacity();
while self.slot_idx < cap {
let idx = self.slot_idx;
self.slot_idx += 1;
if table.control_at(idx).is_occupied() {
let entry = unsafe { table.take(idx) };
table.mark_tombstone(idx);
return Some((entry.key, entry.value));
}
}
self.phase = FunnelIterPhase::Done;
}
FunnelIterPhase::Done => return None,
}
}
}
}
impl<K, V> FusedIterator for FunnelIntoIter<K, V> {}
impl<K, V> Drop for FunnelIntoIter<K, V> {
fn drop(&mut self) {
for _ in self.by_ref() {}
}
}
impl<K, V> fmt::Debug for FunnelIntoIter<K, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FunnelIntoIter")
.field("level_idx", &self.level_idx)
.field("slot_idx", &self.slot_idx)
.finish_non_exhaustive()
}
}
impl<K, V> IntoIterator for FunnelHashMap<K, V>
where
K: Eq + Hash,
{
type Item = (K, V);
type IntoIter = FunnelIntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
FunnelIntoIter {
map: self,
phase: FunnelIterPhase::Levels,
level_idx: 0,
slot_idx: 0,
}
}
}
pub type FunnelIntoKeys<K, V> = CommonIntoKeys<FunnelIntoIter<K, V>>;
pub type FunnelIntoValues<K, V> = CommonIntoValues<FunnelIntoIter<K, V>>;
fn compute_level_count(reserve_fraction: f64) -> usize {
ceil_to_usize((4.0 * (1.0 / reserve_fraction).log2() + 10.0).max(1.0))
}
fn compute_bucket_width(reserve_fraction: f64) -> usize {
ceil_to_usize((2.0 * (1.0 / reserve_fraction).log2()).max(1.0))
}
fn choose_special_capacity(
total_capacity: usize,
reserve_fraction: f64,
bucket_size: usize,
) -> usize {
if total_capacity == 0 {
return 0;
}
let total_capacity_f64 = usize_to_f64(total_capacity);
let lower_bound = ceil_to_usize((reserve_fraction * total_capacity_f64) / 2.0);
let upper_bound = floor_to_usize((3.0 * reserve_fraction * total_capacity_f64) / 4.0);
let lower_bound = lower_bound.min(total_capacity);
let upper_bound = upper_bound.min(total_capacity);
if lower_bound <= upper_bound {
for special_capacity in (lower_bound..=upper_bound).rev() {
if (total_capacity - special_capacity).is_multiple_of(bucket_size.max(1)) {
return special_capacity;
}
}
}
let target = round_to_usize(
((5.0 * reserve_fraction * total_capacity_f64) / 8.0).clamp(0.0, total_capacity_f64),
);
let mut best_special_capacity = total_capacity % bucket_size.max(1);
let mut best_distance = usize::MAX;
for main_capacity in (0..=total_capacity).step_by(bucket_size.max(1)) {
let special_capacity = total_capacity - main_capacity;
let distance = special_capacity.abs_diff(target);
if distance < best_distance {
best_distance = distance;
best_special_capacity = special_capacity;
}
}
best_special_capacity
}
fn partition_funnel_buckets(total_buckets: usize, level_count: usize) -> Vec<usize> {
if level_count == 0 {
return Vec::new();
}
if total_buckets == 0 {
return vec![0; level_count];
}
let first_level_guess = {
let ratio = 0.75f64;
let denom = 1.0 - ratio.powi(i32::try_from(level_count).expect("level count fits in i32"));
if denom <= 0.0 {
total_buckets.max(1)
} else {
round_to_usize((((usize_to_f64(total_buckets)) * (1.0 - ratio)) / denom).max(0.0))
}
};
for radius in 0..=total_buckets {
let lower = first_level_guess.saturating_sub(radius);
if let Some(bucket_counts) = build_funnel_bucket_sequence(total_buckets, level_count, lower)
{
return bucket_counts;
}
let upper = first_level_guess.saturating_add(radius).min(total_buckets);
if upper != lower
&& let Some(bucket_counts) =
build_funnel_bucket_sequence(total_buckets, level_count, upper)
{
return bucket_counts;
}
}
let mut fallback_counts = vec![0; level_count];
fallback_counts[0] = total_buckets;
fallback_counts
}
fn build_funnel_bucket_sequence(
total_buckets: usize,
level_count: usize,
first_level_bucket_count: usize,
) -> Option<Vec<usize>> {
if level_count == 0 || first_level_bucket_count > total_buckets {
return None;
}
let mut bucket_counts = Vec::with_capacity(level_count);
bucket_counts.push(first_level_bucket_count);
let mut remaining = total_buckets.saturating_sub(first_level_bucket_count);
let mut previous_bucket_count = first_level_bucket_count;
for level_idx in 1..level_count {
let levels_after = level_count - level_idx - 1;
let (min_next_bucket_count, max_next_bucket_count) =
next_bucket_count_bounds(previous_bucket_count);
let ideal_next_bucket_count = ((3 * previous_bucket_count + 2) / 4)
.clamp(min_next_bucket_count, max_next_bucket_count);
let mut chosen_bucket_count = None;
let mut best_distance = usize::MAX;
let candidate_upper_bound = max_next_bucket_count.min(remaining);
for candidate_bucket_count in min_next_bucket_count..=candidate_upper_bound {
let remaining_after_candidate = remaining - candidate_bucket_count;
let (tail_min_sum, tail_max_sum) =
possible_tail_sum_range(candidate_bucket_count, levels_after);
if remaining_after_candidate < tail_min_sum || remaining_after_candidate > tail_max_sum
{
continue;
}
let distance = candidate_bucket_count.abs_diff(ideal_next_bucket_count);
if distance < best_distance {
best_distance = distance;
chosen_bucket_count = Some(candidate_bucket_count);
if distance == 0 {
break;
}
}
}
let chosen_bucket_count = chosen_bucket_count?;
bucket_counts.push(chosen_bucket_count);
remaining -= chosen_bucket_count;
previous_bucket_count = chosen_bucket_count;
}
if remaining == 0 {
Some(bucket_counts)
} else {
None
}
}
fn next_bucket_count_bounds(current_bucket_count: usize) -> (usize, usize) {
let scaled = current_bucket_count.saturating_mul(3);
let min_next_bucket_count = scaled.saturating_sub(4).div_ceil(4);
let max_next_bucket_count = scaled.saturating_add(4) / 4;
(
min_next_bucket_count,
max_next_bucket_count.max(min_next_bucket_count),
)
}
fn possible_tail_sum_range(start_bucket_count: usize, levels_after: usize) -> (usize, usize) {
let mut min_sum = 0;
let mut max_sum = 0;
let mut min_previous = start_bucket_count;
let mut max_previous = start_bucket_count;
for _ in 0..levels_after {
let (next_min, _) = next_bucket_count_bounds(min_previous);
let (_, next_max) = next_bucket_count_bounds(max_previous);
min_sum += next_min;
max_sum += next_max;
min_previous = next_min;
max_previous = next_max;
}
(min_sum, max_sum)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn funnel_layout_covers_capacity() {
let capacity = 257;
let map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(capacity);
let level_capacity = map.levels.iter().map(BucketLevel::capacity).sum::<usize>();
let special_capacity =
map.special.primary.table.capacity() + map.special.fallback.table.capacity();
let total = level_capacity + special_capacity;
assert!(
total >= capacity,
"total={total} below requested={capacity}"
);
assert!(
total <= capacity * 2,
"total={total} exceeds 2x of requested={capacity}",
);
}
#[test]
fn insert_get_and_update_work() {
let mut map = FunnelHashMap::with_capacity(512);
for key in 0..20 {
assert_eq!(map.insert(key, key * 10), None);
}
for key in 0..20 {
assert_eq!(map.get(&key), Some(&(key * 10)));
}
let replaced = map.insert(7, 777).expect("update should succeed");
assert_eq!(replaced, 70);
assert_eq!(map.get(&7), Some(&777));
}
#[test]
fn remove_and_clear_work_with_borrowed_keys() {
let mut map: FunnelHashMap<String, i32> = FunnelHashMap::with_capacity(256);
assert_eq!(map.insert("alpha".to_string(), 1), None);
assert_eq!(map.insert("beta".to_string(), 2), None);
assert_eq!(map.remove("alpha"), Some(1));
assert_eq!(map.remove("alpha"), None);
map.clear();
assert_eq!(map.get("beta"), None);
assert!(map.is_empty());
}
#[test]
fn new_starts_empty() {
let map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
assert_eq!(map.capacity(), 0);
assert_eq!(map.len(), 0);
}
#[test]
fn insert_resizes_from_zero_capacity() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
map.insert(1, 10);
assert_eq!(map.get(&1), Some(&10));
assert!(map.capacity() > 0);
}
#[test]
fn insert_resizes_when_threshold_is_reached() {
let capacity = 64;
let mut map = FunnelHashMap::with_capacity(capacity);
let max_insertions = max_insertions(capacity, DEFAULT_RESERVE_FRACTION);
for key in 0..max_insertions + 10 {
let _ = map.insert(key, key * 10);
}
for key in 0..max_insertions + 10 {
assert_eq!(map.get(&key), Some(&(key * 10)));
}
assert!(map.capacity() > capacity);
}
#[test]
fn options_constructor_preserves_capacity() {
let map: FunnelHashMap<i32, i32> = FunnelHashMap::with_options(FunnelOptions {
capacity: 320,
reserve_fraction: DEFAULT_RESERVE_FRACTION,
primary_probe_limit: Some(4),
});
assert_eq!(map.capacity(), 320);
}
#[test]
fn delete_heavy_preserves_correctness() {
let mut map = FunnelHashMap::with_capacity(400);
for i in 0..200 {
map.insert(i, i * 10);
}
for i in 0..160 {
assert_eq!(map.remove(&i), Some(i * 10));
}
for i in 160..200 {
assert_eq!(
map.get(&i),
Some(&(i * 10)),
"key {i} missing after deletes"
);
}
assert_eq!(map.len(), 40);
for i in 1000..1100 {
assert_eq!(map.insert(i, i), None);
}
for i in 1000..1100 {
assert_eq!(map.get(&i), Some(&i), "key {i} missing after re-insert");
}
}
#[test]
fn large_map_correctness() {
let n = 10_000;
let mut map = FunnelHashMap::with_capacity(n * 2);
for i in 0..n {
assert_eq!(map.insert(i, i), None);
}
for i in 0..n {
assert_eq!(map.get(&i), Some(&i), "key {i} missing");
}
assert_eq!(map.len(), n);
}
#[test]
fn interleaved_insert_delete_correctness() {
let mut map = FunnelHashMap::with_capacity(256);
for i in 0..100 {
map.insert(i, i);
}
for i in (1..100).step_by(2) {
assert!(map.remove(&i).is_some());
}
for i in (0..100).step_by(2) {
assert_eq!(map.get(&i), Some(&i), "even key {i} missing");
}
for i in (1..100).step_by(2) {
assert_eq!(map.get(&i), None, "odd key {i} should be gone");
}
}
#[test]
fn special_primary_single_group_edge_case() {
let mut map: FunnelHashMap<u64, u64> = FunnelHashMap::with_capacity(1);
for i in 0..16 {
map.insert(i, i * 3);
}
for i in 0..16 {
assert_eq!(map.get(&i), Some(&(i * 3)));
}
for i in 0..8 {
assert_eq!(map.remove(&i), Some(i * 3));
}
for i in 0..8 {
assert_eq!(map.get(&i), None);
}
for i in 8..16 {
assert_eq!(map.get(&i), Some(&(i * 3)));
}
}
#[test]
fn delete_insert_cycles_trigger_rebuild() {
let n = 12_000;
let mut map = FunnelHashMap::with_capacity(n * 2);
for i in 0..n {
map.insert(i, i);
}
for i in 0..6000 {
assert!(map.remove(&i).is_some(), "remove {i} failed");
map.insert(i + n, i + n);
}
assert_eq!(map.len(), n);
for i in 6000..n {
assert_eq!(map.get(&i), Some(&i), "original key {i} missing");
}
for i in 0..6000 {
assert_eq!(
map.get(&(i + n)),
Some(&(i + n)),
"new key {} missing",
i + n
);
}
}
#[test]
fn iter_yields_every_inserted_pair_once() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(128);
for i in 0..80 {
map.insert(i, i * 7);
}
let mut collected: Vec<(i32, i32)> = map.iter().map(|(&k, &v)| (k, v)).collect();
collected.sort();
let expected: Vec<(i32, i32)> = (0..80).map(|i| (i, i * 7)).collect();
assert_eq!(collected, expected);
}
#[test]
fn iter_skips_tombstones_after_remove() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(64);
for i in 0..40 {
map.insert(i, i);
}
for i in (0..40).step_by(3) {
map.remove(&i);
}
let keys: Vec<i32> = map.iter().map(|(&k, _)| k).collect();
assert_eq!(keys.len(), map.len());
}
#[test]
fn iter_empty_map_is_empty() {
let map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
assert_eq!(map.iter().count(), 0);
}
#[test]
fn remove_preserves_sibling_when_level_zero_empty() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::default();
map.insert(1, 10);
map.insert(2, 20);
map.remove(&1);
assert_eq!(map.get(&2), Some(&20));
assert_eq!(map.len(), 1);
}
#[test]
fn get_disjoint_mut_returns_all_refs_on_hits() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(64);
for i in 0..16 {
map.insert(i, i * 10);
}
let got = map.get_disjoint_mut([&1, &3, &7, &15]).expect("all hits");
assert_eq!(*got[0], 10);
assert_eq!(*got[1], 30);
assert_eq!(*got[2], 70);
assert_eq!(*got[3], 150);
}
#[test]
fn get_disjoint_mut_returns_none_if_any_missing() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(32);
for i in 0..8 {
map.insert(i, i);
}
assert!(map.get_disjoint_mut([&0, &1, &99]).is_none());
}
#[test]
#[should_panic(expected = "duplicate keys")]
fn get_disjoint_mut_panics_on_duplicate_keys() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(32);
map.insert(1, 100);
map.insert(2, 200);
let _ = map.get_disjoint_mut([&1, &1]);
}
#[test]
fn get_disjoint_mut_zero_keys_is_some_empty() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(16);
map.insert(1, 1);
let got: [&mut i32; 0] = map
.get_disjoint_mut::<i32, 0>([])
.expect("zero-key returns Some");
assert_eq!(got.len(), 0);
}
#[test]
fn get_disjoint_mut_mutation_propagates() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(32);
for i in 0..8 {
map.insert(i, i);
}
{
let got = map.get_disjoint_mut([&2, &5]).expect("hit");
*got[0] = 222;
*got[1] = 555;
}
assert_eq!(map.get(&2), Some(&222));
assert_eq!(map.get(&5), Some(&555));
}
#[test]
fn keys_yields_inserted_keys_only() {
use std::collections::HashSet;
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(128);
for i in 0..50 {
map.insert(i, i * 7);
}
let got: HashSet<i32> = map.keys().copied().collect();
let expected: HashSet<i32> = (0..50).collect();
assert_eq!(got, expected);
}
#[test]
fn values_yields_inserted_values_only() {
use std::collections::HashSet;
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(128);
for i in 0..50 {
map.insert(i, i * 7);
}
let got: HashSet<i32> = map.values().copied().collect();
let expected: HashSet<i32> = (0..50).map(|i| i * 7).collect();
assert_eq!(got, expected);
}
#[test]
fn hasher_returns_consistent_handle() {
let map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
let a: *const _ = map.hasher();
let b: *const _ = map.hasher();
assert!(std::ptr::eq(a, b));
}
#[test]
fn get_key_value_returns_both_on_hit_none_on_miss() {
let mut map: FunnelHashMap<String, i32> = FunnelHashMap::with_capacity(16);
map.insert("alpha".to_string(), 1);
map.insert("beta".to_string(), 2);
let (k, v) = map.get_key_value("alpha").expect("hit");
assert_eq!(k, "alpha");
assert_eq!(*v, 1);
assert!(map.get_key_value("missing").is_none());
}
#[test]
fn remove_entry_returns_both_and_actually_removes() {
let mut map: FunnelHashMap<String, i32> = FunnelHashMap::with_capacity(16);
map.insert("alpha".to_string(), 1);
map.insert("beta".to_string(), 2);
let (k, v) = map.remove_entry("alpha").expect("hit");
assert_eq!(k, "alpha");
assert_eq!(v, 1);
assert_eq!(map.len(), 1);
assert!(map.get("alpha").is_none());
assert!(map.remove_entry("alpha").is_none());
}
#[test]
fn iter_mut_yields_mutable_values_in_some_order() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(128);
for i in 0..50 {
map.insert(i, i);
}
for (_, v) in &mut map {
*v *= 2;
}
for i in 0..50 {
assert_eq!(map.get(&i), Some(&(i * 2)), "key {i} not doubled");
}
}
#[test]
fn try_reserve_grows_when_needed() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
assert_eq!(map.capacity(), 0);
map.try_reserve(1024).expect("alloc should succeed");
let cap = map.capacity();
assert!(cap >= 1024, "reserve under-allocated: cap={cap}");
for i in 0..1024 {
map.insert(i, i * 2);
}
for i in 0..1024 {
assert_eq!(map.get(&i), Some(&(i * 2)));
}
assert_eq!(map.len(), 1024);
}
#[test]
fn try_reserve_zero_additional_is_noop() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(128);
let cap_before = map.capacity();
map.try_reserve(0).expect("noop");
assert_eq!(map.capacity(), cap_before);
}
#[test]
fn try_reserve_overflow_returns_error() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
map.insert(1, 1);
assert_eq!(
map.try_reserve(usize::MAX),
Err(TryReserveError::CapacityOverflow)
);
}
#[test]
fn shrink_to_below_len_clamps_to_len() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(4096);
for i in 0..200 {
map.insert(i, i);
}
let cap_before = map.capacity();
map.shrink_to(0);
let cap_after = map.capacity();
assert!(cap_after < cap_before);
assert!(cap_after >= map.len());
for i in 0..200 {
assert_eq!(map.get(&i), Some(&i));
}
}
#[test]
fn iter_mut_yields_each_entry_exactly_once() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(128);
for i in 0..80 {
map.insert(i, i * 3);
}
let mut collected: Vec<(i32, i32)> = map.iter_mut().map(|(&k, v)| (k, *v)).collect();
collected.sort_unstable();
let expected: Vec<(i32, i32)> = (0..80).map(|i| (i, i * 3)).collect();
assert_eq!(collected, expected);
}
#[test]
fn iter_mut_skips_tombstones() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(64);
for i in 0..40 {
map.insert(i, i);
}
for i in (0..40).step_by(2) {
map.remove(&i);
}
let count = map.iter_mut().count();
assert_eq!(count, map.len());
}
#[test]
fn iter_mut_empty_map_is_empty() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
assert_eq!(map.iter_mut().count(), 0);
}
#[test]
fn values_mut_mutates_in_place() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(64);
for i in 0..30 {
map.insert(i, i);
}
for v in map.values_mut() {
*v += 100;
}
for i in 0..30 {
assert_eq!(map.get(&i), Some(&(i + 100)));
}
}
#[test]
fn retain_keeps_matching_drops_rest() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(256);
for i in 0..80 {
map.insert(i, i * 10);
}
map.retain(|k, _| k % 2 == 0);
assert_eq!(map.len(), 40);
for i in 0..80 {
if i % 2 == 0 {
assert_eq!(map.get(&i), Some(&(i * 10)));
} else {
assert!(map.get(&i).is_none());
}
}
}
#[test]
fn retain_with_empty_map_is_noop() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
let mut called = false;
map.retain(|_, _| {
called = true;
true
});
assert!(!called);
assert!(map.is_empty());
}
#[test]
fn retain_can_mutate_values_in_place() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(256);
for i in 0..40 {
map.insert(i, i);
}
map.retain(|k, v| {
*v += 100;
k % 2 == 0
});
assert_eq!(map.len(), 20);
for i in (0..40).step_by(2) {
assert_eq!(map.get(&i), Some(&(i + 100)));
}
}
#[test]
fn into_iter_yields_all_entries() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(128);
for i in 0..60 {
map.insert(i, i * 11);
}
let mut collected: Vec<(i32, i32)> = map.into_iter().collect();
collected.sort_unstable();
let expected: Vec<(i32, i32)> = (0..60).map(|i| (i, i * 11)).collect();
assert_eq!(collected, expected);
}
#[test]
fn into_iter_skips_tombstones() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(128);
for i in 0..60 {
map.insert(i, i);
}
for i in (0..60).step_by(3) {
map.remove(&i);
}
let expected_len = map.len();
let collected: Vec<(i32, i32)> = map.into_iter().collect();
assert_eq!(collected.len(), expected_len);
}
#[test]
fn into_keys_yields_all_keys() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(64);
for i in 0..30 {
map.insert(i, i);
}
let mut keys: Vec<i32> = map.into_keys().collect();
keys.sort_unstable();
assert_eq!(keys, (0..30).collect::<Vec<_>>());
}
#[test]
fn into_values_yields_all_values() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(64);
for i in 0..30 {
map.insert(i, i * 5);
}
let mut vals: Vec<i32> = map.into_values().collect();
vals.sort_unstable();
let expected: Vec<i32> = (0..30).map(|i| i * 5).collect();
assert_eq!(vals, expected);
}
#[test]
fn into_keys_drops_values() {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
struct DropCounter {
counter: Arc<AtomicUsize>,
}
impl Drop for DropCounter {
fn drop(&mut self) {
self.counter.fetch_add(1, Ordering::SeqCst);
}
}
let counter = Arc::new(AtomicUsize::new(0));
let n: usize = 32;
let mut map: FunnelHashMap<usize, DropCounter> = FunnelHashMap::with_capacity(64);
for i in 0..n {
map.insert(
i,
DropCounter {
counter: Arc::clone(&counter),
},
);
}
let keys: Vec<usize> = map.into_keys().collect();
assert_eq!(keys.len(), n);
assert_eq!(counter.load(Ordering::SeqCst), n);
}
#[test]
fn into_values_drops_keys() {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
struct DropKey {
id: usize,
counter: Arc<AtomicUsize>,
}
impl std::hash::Hash for DropKey {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl PartialEq for DropKey {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for DropKey {}
impl Drop for DropKey {
fn drop(&mut self) {
self.counter.fetch_add(1, Ordering::SeqCst);
}
}
let counter = Arc::new(AtomicUsize::new(0));
let n: usize = 32;
let mut map: FunnelHashMap<DropKey, usize> = FunnelHashMap::with_capacity(64);
for i in 0..n {
map.insert(
DropKey {
id: i,
counter: Arc::clone(&counter),
},
i,
);
}
let vals: Vec<usize> = map.into_values().collect();
assert_eq!(vals.len(), n);
assert_eq!(counter.load(Ordering::SeqCst), n);
}
#[test]
fn iter_mut_partial_consume_then_drop() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(128);
for i in 0..40 {
map.insert(i, i);
}
{
let mut it = map.iter_mut();
for _ in 0..7 {
if let Some((_, v)) = it.next() {
*v += 1000;
}
}
}
assert_eq!(map.len(), 40);
for i in 0..40 {
assert!(map.get(&i).is_some(), "key {i} disappeared");
}
}
#[test]
fn into_iter_partial_drop_drops_remaining() {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
struct DropCounter {
counter: Arc<AtomicUsize>,
}
impl Drop for DropCounter {
fn drop(&mut self) {
self.counter.fetch_add(1, Ordering::SeqCst);
}
}
let counter = Arc::new(AtomicUsize::new(0));
let n: usize = 50;
let mut map: FunnelHashMap<usize, DropCounter> = FunnelHashMap::with_capacity(128);
for i in 0..n {
map.insert(
i,
DropCounter {
counter: Arc::clone(&counter),
},
);
}
let take = 12;
let mut it = map.into_iter();
let mut taken: Vec<(usize, DropCounter)> = Vec::with_capacity(take);
for _ in 0..take {
taken.push(it.next().expect("element"));
}
drop(it);
assert_eq!(counter.load(Ordering::SeqCst), n - take);
drop(taken);
assert_eq!(counter.load(Ordering::SeqCst), n);
}
#[test]
fn entry_or_insert_creates_when_missing() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
let value = map.entry(1).or_insert(10);
assert_eq!(*value, 10);
assert_eq!(map.get(&1), Some(&10));
assert_eq!(map.len(), 1);
}
#[test]
fn entry_or_insert_returns_existing() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
map.insert(1, 10);
let value = map.entry(1).or_insert(99);
assert_eq!(*value, 10);
assert_eq!(map.get(&1), Some(&10));
assert_eq!(map.len(), 1);
}
#[test]
fn entry_or_insert_with_lazy_default_not_called_on_hit() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
map.insert(1, 10);
let mut called = false;
let value = map.entry(1).or_insert_with(|| {
called = true;
42
});
assert_eq!(*value, 10);
assert!(!called, "default closure must not run on occupied entry");
}
#[test]
fn entry_or_insert_with_key_uses_key_in_default() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
let value = map.entry(7).or_insert_with_key(|k| k * 100);
assert_eq!(*value, 700);
assert_eq!(map.get(&7), Some(&700));
}
#[test]
fn entry_and_modify_runs_on_occupied() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
map.insert(1, 10);
let value = map.entry(1).and_modify(|v| *v += 5).or_insert(0);
assert_eq!(*value, 15);
assert_eq!(map.get(&1), Some(&15));
}
#[test]
fn entry_and_modify_skips_on_vacant() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
let mut touched = false;
let value = map.entry(1).and_modify(|_| touched = true).or_insert(42);
assert_eq!(*value, 42);
assert!(!touched);
assert_eq!(map.get(&1), Some(&42));
}
#[test]
fn entry_occupied_get_mut_mutates() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
map.insert(1, 10);
if let Entry::Occupied(mut occ) = map.entry(1) {
*occ.get_mut() = 99;
assert_eq!(*occ.get(), 99);
} else {
panic!("expected occupied");
}
assert_eq!(map.get(&1), Some(&99));
}
#[test]
fn entry_occupied_into_mut_outlives_entry_borrow() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
map.insert(1, 10);
let value: &mut i32 = match map.entry(1) {
Entry::Occupied(occ) => occ.into_mut(),
Entry::Vacant(_) => panic!("expected occupied"),
};
*value = 123;
assert_eq!(map.get(&1), Some(&123));
}
#[test]
fn entry_occupied_insert_returns_old_and_replaces() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
map.insert(1, 10);
if let Entry::Occupied(mut occ) = map.entry(1) {
let old = occ.insert(99);
assert_eq!(old, 10);
} else {
panic!("expected occupied");
}
assert_eq!(map.get(&1), Some(&99));
}
#[test]
fn entry_occupied_remove_returns_value() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
map.insert(1, 10);
map.insert(2, 20);
if let Entry::Occupied(occ) = map.entry(1) {
assert_eq!(occ.remove(), 10);
} else {
panic!("expected occupied");
}
assert!(map.get(&1).is_none());
assert_eq!(map.get(&2), Some(&20));
assert_eq!(map.len(), 1);
}
#[test]
fn entry_vacant_insert_returns_mut_ref() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
let value: &mut i32 = match map.entry(5) {
Entry::Vacant(vac) => vac.insert(50),
Entry::Occupied(_) => panic!("expected vacant"),
};
*value += 1;
assert_eq!(map.get(&5), Some(&51));
}
#[test]
fn try_insert_succeeds_when_missing() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
let value = map.try_insert(1, 10).expect("vacant should succeed");
assert_eq!(*value, 10);
assert_eq!(map.get(&1), Some(&10));
}
#[test]
fn try_insert_fails_with_occupied_error_when_present() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
map.insert(1, 10);
let err = map.try_insert(1, 99).expect_err("occupied must error");
assert_eq!(err.entry.key(), &1);
assert_eq!(err.entry.get(), &10);
assert_eq!(map.get(&1), Some(&10));
}
#[test]
fn try_insert_occupied_error_carries_rejected_value() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::new();
map.insert(1, 10);
let err = map.try_insert(1, 99).expect_err("occupied must error");
assert_eq!(err.value, 99);
}
#[test]
fn drain_yields_all_entries_then_empties_map() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(256);
for i in 0..60 {
map.insert(i, i * 7);
}
let mut collected: Vec<(i32, i32)> = map.drain().collect();
collected.sort_unstable();
let expected: Vec<(i32, i32)> = (0..60).map(|i| (i, i * 7)).collect();
assert_eq!(collected, expected);
assert!(map.is_empty());
assert_eq!(map.iter().count(), 0);
map.insert(999, 999);
assert_eq!(map.get(&999), Some(&999));
}
#[test]
fn drain_partial_consume_then_drop_still_empties_map() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(256);
for i in 0..60 {
map.insert(i, i);
}
{
let mut drain = map.drain();
let _first = drain.next();
let _second = drain.next();
}
assert!(map.is_empty());
assert_eq!(map.iter().count(), 0);
}
#[test]
fn extract_if_yields_only_matching_and_leaves_rest() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(256);
for i in 0..80 {
map.insert(i, i);
}
let mut extracted: Vec<(i32, i32)> = map.extract_if(|k, _| k % 3 == 0).collect();
extracted.sort_unstable();
let expected: Vec<(i32, i32)> = (0..80).filter(|i| i % 3 == 0).map(|i| (i, i)).collect();
assert_eq!(extracted, expected);
assert_eq!(map.len(), 80 - expected.len());
for i in 0..80 {
if i % 3 == 0 {
assert!(map.get(&i).is_none());
} else {
assert_eq!(map.get(&i), Some(&i));
}
}
}
#[test]
fn extract_if_partial_consume_then_drop_keeps_remaining_in_map() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(256);
for i in 0..60 {
map.insert(i, i);
}
let original_len = map.len();
let extracted_count;
{
let mut it = map.extract_if(|_, _| true);
assert!(it.next().is_some());
assert!(it.next().is_some());
extracted_count = 2;
}
assert_eq!(map.len(), original_len - extracted_count);
let remaining: Vec<i32> = map.iter().map(|(&k, _)| k).collect();
assert_eq!(remaining.len(), original_len - extracted_count);
}
#[test]
fn retain_does_not_trigger_mid_iter_resize_with_clustered_tombstones() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(1024);
let max = i32::try_from(max_insertions(map.capacity(), DEFAULT_RESERVE_FRACTION))
.expect("test capacity fits i32");
for i in 0..max {
map.insert(i, i);
}
let initial_capacity = map.capacity();
map.retain(|k, _| k % 2 == 0);
let expected_count = (0..max).filter(|i| i % 2 == 0).count();
assert_eq!(map.len(), expected_count);
for i in 0..max {
if i % 2 == 0 {
assert_eq!(map.get(&i), Some(&i), "kept key {i} missing");
} else {
assert!(map.get(&i).is_none(), "dropped key {i} survived");
}
}
assert!(map.capacity() <= initial_capacity * 2);
}
#[test]
fn shrink_to_above_capacity_is_noop() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(256);
for i in 0..20 {
map.insert(i, i);
}
let cap = map.capacity();
map.shrink_to(cap * 4);
assert_eq!(map.capacity(), cap);
}
#[test]
fn shrink_to_fit_reduces_capacity_when_sparse() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(4096);
for i in 0..2000 {
map.insert(i, i);
}
for i in 0..1800 {
map.remove(&i);
}
let cap_before = map.capacity();
map.shrink_to_fit();
assert!(map.capacity() < cap_before);
for i in 1800..2000 {
assert_eq!(map.get(&i), Some(&i));
}
}
#[test]
fn shrink_then_insert_works() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(2048);
for i in 0..400 {
map.insert(i, i * 3);
}
for i in 0..300 {
map.remove(&i);
}
map.shrink_to_fit();
for i in 0..100 {
assert_eq!(map.insert(i, i * 5), None);
}
for i in 0..100 {
assert_eq!(map.get(&i), Some(&(i * 5)));
}
for i in 300..400 {
assert_eq!(map.get(&i), Some(&(i * 3)));
}
}
}