use core::iter::FusedIterator;
use core::marker::PhantomData;
use core::mem::ManuallyDrop;
use core::mem::MaybeUninit;
#[cfg(all(feature = "nightly", not(feature = "allocator-api2")))]
use alloc::{alloc::Allocator, alloc::Global, boxed::Box, vec::Vec};
#[cfg(feature = "allocator-api2")]
use allocator_api2::{alloc::Allocator, alloc::Global, boxed::Box, vec::Vec};
#[cfg(not(any(feature = "allocator-api2", feature = "nightly")))]
use {
crate::__alloc::{Allocator, Global},
alloc::{boxed::Box, vec::Vec},
};
pub struct RawTable<T, S: Status = usize, A: Allocator + Clone = Global> {
#[cfg(any(feature = "allocator-api2", feature = "nightly"))]
data: Box<[Slot<T, S>], A>,
#[cfg(not(any(feature = "allocator-api2", feature = "nightly")))]
data: Box<[Slot<T, S>]>,
len: usize,
free: usize,
phantom: PhantomData<A>,
}
struct Slot<T, S: Status = usize> {
status: S,
data: MaybeUninit<T>,
}
pub struct Iter<'a, T, S: Status = usize> {
iter: core::slice::Iter<'a, Slot<T, S>>,
len: usize,
}
pub struct IterMut<'a, T, S: Status = usize> {
iter: core::slice::IterMut<'a, Slot<T, S>>,
len: usize,
}
pub struct IntoIter<T, S: Status = usize, A: Allocator = Global> {
#[cfg(feature = "allocator-api2")]
iter: allocator_api2::vec::IntoIter<Slot<T, S>, A>,
#[cfg(all(not(feature = "allocator-api2"), feature = "nightly"))]
iter: alloc::vec::IntoIter<Slot<T, S>, A>,
#[cfg(not(any(feature = "allocator-api2", feature = "nightly")))]
iter: alloc::vec::IntoIter<Slot<T, S>>,
len: usize,
phantom: PhantomData<A>,
}
pub struct Drain<'a, T, S: Status = usize> {
iter: core::slice::IterMut<'a, Slot<T, S>>,
len: usize,
}
pub unsafe trait Status: Copy + Eq {
const FREE: Self;
const TOMBSTONE: Self;
fn from_hash(hash: u64) -> Self;
fn check_capacity(capacity: usize);
fn is_hash(self) -> bool;
fn hash_as_usize(self) -> usize;
}
unsafe impl Status for usize {
const FREE: Self = usize::MAX;
const TOMBSTONE: Self = usize::MAX - 1;
#[inline]
fn from_hash(hash: u64) -> Self {
hash as usize & (usize::MAX >> 1)
}
#[inline]
#[track_caller]
fn check_capacity(capacity: usize) {
assert!(
capacity <= 1 << (usize::BITS - 1),
"requested capacity {capacity} is too large"
);
}
#[inline]
fn is_hash(self) -> bool {
self >> (usize::BITS - 1) == 0 }
#[inline]
fn hash_as_usize(self) -> usize {
debug_assert!(self.is_hash());
self
}
}
unsafe impl Status for u32 {
const FREE: Self = u32::MAX;
const TOMBSTONE: Self = u32::MAX - 1;
#[inline]
fn from_hash(hash: u64) -> Self {
hash as u32 & (u32::MAX >> 1)
}
#[inline]
#[track_caller]
fn check_capacity(capacity: usize) {
assert!(
capacity <= 1 << (u32::BITS - 1),
"requested capacity {capacity} is too large"
);
}
#[inline]
fn is_hash(self) -> bool {
self >> (u32::BITS - 1) == 0 }
#[inline]
fn hash_as_usize(self) -> usize {
debug_assert!(self.is_hash());
self as usize
}
}
impl<T, S: Status> Slot<T, S> {
const FREE: Self = Slot {
status: S::FREE,
data: MaybeUninit::uninit(),
};
}
impl<T: Clone, S: Status> Clone for Slot<T, S> {
fn clone(&self) -> Self {
Self {
status: self.status,
data: if self.status.is_hash() {
MaybeUninit::new(unsafe { self.data.assume_init_ref() }.clone())
} else {
MaybeUninit::uninit()
},
}
}
}
impl<T, S: Status> RawTable<T, S> {
#[inline]
pub fn new() -> Self {
RawTable {
data: Vec::new().into_boxed_slice(),
len: 0,
free: 0,
phantom: PhantomData,
}
}
pub fn with_capacity(capacity: usize) -> Self {
let capacity = Self::next_capacity(capacity);
let mut data = Vec::with_capacity(capacity);
data.resize_with(capacity, || Slot::FREE);
RawTable {
data: data.into_boxed_slice(),
len: 0,
free: capacity,
phantom: PhantomData,
}
}
}
#[cfg(any(feature = "allocator-api2", feature = "nightly"))]
impl<T, S: Status, A: Clone + Allocator> RawTable<T, S, A> {
#[inline]
pub fn new_in(alloc: A) -> Self {
RawTable {
data: Vec::new_in(alloc).into_boxed_slice(),
len: 0,
free: 0,
phantom: PhantomData,
}
}
pub fn with_capacity_in(capacity: usize, alloc: A) -> Self {
let capacity = Self::next_capacity(capacity);
let mut data = Vec::with_capacity_in(capacity, alloc);
data.resize_with(capacity, || Slot::FREE);
RawTable {
data: data.into_boxed_slice(),
len: 0,
free: capacity,
phantom: PhantomData,
}
}
}
const RATIO_N: usize = 3;
const RATIO_D: usize = 4;
const MIN_CAP: usize = 16;
impl<T, S: Status, A: Clone + Allocator> RawTable<T, S, A> {
#[inline]
fn next_capacity(requested: usize) -> usize {
if requested == 0 {
return 0;
}
let capacity = core::cmp::max((requested * RATIO_D / RATIO_N).next_power_of_two(), MIN_CAP);
S::check_capacity(capacity);
capacity
}
#[inline(always)]
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn capacity(&self) -> usize {
self.data.len() / RATIO_D * RATIO_N
}
#[inline]
pub fn slots(&self) -> usize {
self.data.len()
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
let spare = additional + self.data.len() / RATIO_D * (RATIO_D - RATIO_N);
if self.free < spare {
self.reserve_rehash(additional)
}
}
#[cold]
fn reserve_rehash(&mut self, additional: usize) {
let new_cap = Self::next_capacity(self.len + additional);
#[cfg(any(feature = "allocator-api2", feature = "nightly"))]
let mut new_data = Vec::with_capacity_in(new_cap, Box::allocator(&self.data).clone());
#[cfg(not(any(feature = "allocator-api2", feature = "nightly")))]
let mut new_data = Vec::with_capacity(new_cap);
new_data.resize_with(new_cap, || Slot::FREE);
let old_data = core::mem::replace(&mut self.data, new_data.into_boxed_slice());
if new_cap == 0 {
self.free = 0;
return;
}
let new_data = &mut self.data[..];
let new_mask = new_cap - 1;
for slot in old_data.into_vec() {
let status = slot.status;
if !status.is_hash() {
continue;
}
let data = unsafe { slot.data.assume_init() };
let mut index = status.hash_as_usize() & new_mask;
loop {
let new_slot = unsafe { new_data.get_unchecked_mut(index) };
if new_slot.status == S::FREE {
new_slot.data.write(data);
new_slot.status = status;
break;
}
index = (index + 1) & new_mask;
}
}
self.free = new_cap - self.len;
}
pub fn clear(&mut self) {
if self.len == 0 {
return;
}
for slot in self.data.iter_mut() {
let status = slot.status;
slot.status = S::FREE;
if status.is_hash() {
unsafe { slot.data.assume_init_drop() };
self.len -= 1;
if self.len == 0 {
return;
}
}
}
unsafe { core::hint::unreachable_unchecked() }
}
pub fn clear_no_drop(&mut self) {
if self.len == 0 {
return;
}
for slot in self.data.iter_mut() {
let status = slot.status;
slot.status = S::FREE;
if status.is_hash() {
self.len -= 1;
if self.len == 0 {
return;
}
}
}
unsafe { core::hint::unreachable_unchecked() }
}
#[inline]
pub fn reset_no_drop(&mut self) {
self.len = 0;
#[cfg(any(feature = "allocator-api2", feature = "nightly"))]
let empty = Vec::new_in(Box::allocator(&self.data).clone());
#[cfg(not(any(feature = "allocator-api2", feature = "nightly")))]
let empty = Vec::new();
self.data = empty.into_boxed_slice();
}
#[inline(always)]
pub unsafe fn is_slot_occupied_unchecked(&self, slot: usize) -> bool {
debug_assert!(slot < self.data.len());
unsafe { self.data.get_unchecked(slot) }.status.is_hash()
}
#[inline]
pub fn find(&self, hash: u64, eq: impl Fn(&T) -> bool) -> Option<usize> {
if self.len == 0 {
return None;
}
debug_assert_ne!(self.free, 0, "find may diverge");
debug_assert!(self.data.len().is_power_of_two());
let mask = self.data.len() - 1;
let mut index = hash as usize & mask;
let hash_status = S::from_hash(hash);
loop {
let slot = unsafe { self.data.get_unchecked(index) };
if slot.status == hash_status {
if eq(unsafe { slot.data.assume_init_ref() }) {
return Some(index);
}
} else if slot.status == S::FREE {
return None;
}
index = (index + 1) & mask;
}
}
#[inline]
pub fn find_or_find_insert_slot(
&mut self,
hash: u64,
eq: impl Fn(&T) -> bool,
) -> Result<usize, usize> {
self.reserve(1);
debug_assert!(self.data.len().is_power_of_two());
let mask = self.data.len() - 1;
let mut index = hash as usize & mask;
let mut first_tombstone = None;
let hash_status = S::from_hash(hash);
loop {
let slot = unsafe { self.data.get_unchecked(index) };
if slot.status == hash_status {
if eq(unsafe { slot.data.assume_init_ref() }) {
return Ok(index);
}
} else if slot.status == S::FREE {
return Err(first_tombstone.unwrap_or(index));
} else if slot.status == S::TOMBSTONE && first_tombstone.is_none() {
first_tombstone = Some(index);
}
index = (index + 1) & mask;
}
}
#[inline]
pub fn get(&self, hash: u64, eq: impl Fn(&T) -> bool) -> Option<&T> {
let index = self.find(hash, eq)?;
debug_assert!(self.data[index].status.is_hash());
Some(unsafe { self.data.get_unchecked(index).data.assume_init_ref() })
}
#[inline]
pub fn get_mut(&mut self, hash: u64, eq: impl Fn(&T) -> bool) -> Option<&mut T> {
let index = self.find(hash, eq)?;
debug_assert!(self.data[index].status.is_hash());
Some(unsafe { self.data.get_unchecked_mut(index).data.assume_init_mut() })
}
#[inline]
pub unsafe fn get_at_slot_unchecked(&self, slot: usize) -> &T {
debug_assert!(self.data[slot].status.is_hash(), "slot is empty");
unsafe { self.data.get_unchecked(slot).data.assume_init_ref() }
}
#[inline]
pub unsafe fn get_at_slot_unchecked_mut(&mut self, slot: usize) -> &mut T {
debug_assert!(self.data[slot].status.is_hash(), "slot is empty");
unsafe { self.data.get_unchecked_mut(slot).data.assume_init_mut() }
}
#[inline]
pub unsafe fn insert_in_slot_unchecked(&mut self, hash: u64, slot: usize, val: T) -> &mut T {
debug_assert!(!self.data[slot].status.is_hash(), "slot is occupied");
let slot = unsafe { self.data.get_unchecked_mut(slot) };
if slot.status != S::TOMBSTONE {
debug_assert!(slot.status == S::FREE);
self.free -= 1;
}
self.len += 1;
let res = slot.data.write(val);
slot.status = S::from_hash(hash);
res
}
#[inline]
pub fn remove_entry(&mut self, hash: u64, eq: impl Fn(&T) -> bool) -> Option<T> {
let index = self.find(hash, eq)?;
Some(unsafe { self.remove_at_slot_unchecked(index) })
}
#[inline]
pub unsafe fn remove_at_slot_unchecked(&mut self, slot: usize) -> T {
debug_assert_ne!(self.len, 0);
debug_assert!(self.data[slot].status.is_hash());
let next_slot_index = (slot + 1) & (self.data.len() - 1);
let next_slot_status = unsafe { self.data.get_unchecked(next_slot_index) }.status;
let slot = unsafe { self.data.get_unchecked_mut(slot) };
slot.status = if next_slot_status == S::FREE {
self.free += 1;
S::FREE
} else {
S::TOMBSTONE
};
self.len -= 1;
unsafe { slot.data.assume_init_read() }
}
#[inline]
pub fn iter(&self) -> Iter<'_, T, S> {
Iter {
iter: self.data.iter(),
len: self.len,
}
}
#[inline]
pub fn iter_mut(&mut self) -> IterMut<'_, T, S> {
IterMut {
iter: self.data.iter_mut(),
len: self.len,
}
}
#[inline]
pub fn drain(&mut self) -> Drain<'_, T, S> {
let len = self.len;
self.len = 0;
self.free = self.data.len();
Drain {
iter: self.data.iter_mut(),
len,
}
}
pub fn retain(&mut self, mut predicate: impl FnMut(&mut T) -> bool, mut drop: impl FnMut(T)) {
if self.len == 0 {
return;
}
debug_assert!(self.data.len() >= self.len);
let mut i = self.len;
let mut last_is_free = self.data[0].status == S::FREE;
for slot in self.data.iter_mut().rev() {
if !slot.status.is_hash() {
if slot.status == S::FREE {
last_is_free = true;
} else {
debug_assert!(slot.status == S::TOMBSTONE);
if last_is_free {
slot.status = S::FREE;
self.free += 1;
} else {
last_is_free = false;
}
}
continue;
}
if !predicate(unsafe { slot.data.assume_init_mut() }) {
self.len -= 1;
if last_is_free {
slot.status = S::FREE;
self.free += 1;
} else {
slot.status = S::TOMBSTONE;
}
drop(unsafe { slot.data.assume_init_read() });
} else {
last_is_free = false;
}
i -= 1;
if i == 0 {
if self.len < self.data.len() / RATIO_D * (RATIO_D - RATIO_N)
&& self.data.len() >= MIN_CAP
{
self.reserve_rehash(0);
}
return;
}
}
unsafe { core::hint::unreachable_unchecked() }
}
}
impl<T: Clone, S: Status, A: Clone + Allocator> Clone for RawTable<T, S, A> {
#[inline]
fn clone(&self) -> Self {
Self {
data: self.data.clone(),
len: self.len,
free: self.free,
phantom: PhantomData,
}
}
}
impl<T, S: Status, A: Clone + Default + Allocator> Default for RawTable<T, S, A> {
fn default() -> Self {
RawTable {
#[cfg(any(feature = "allocator-api2", feature = "nightly"))]
data: Vec::new_in(A::default()).into_boxed_slice(),
#[cfg(not(any(feature = "allocator-api2", feature = "nightly")))]
data: Vec::new().into_boxed_slice(),
len: 0,
free: 0,
phantom: PhantomData,
}
}
}
impl<T, S: Status, A: Clone + Allocator> Drop for RawTable<T, S, A> {
fn drop(&mut self) {
if core::mem::needs_drop::<T>() {
self.clear();
}
}
}
impl<T, S: Status, A: Clone + Allocator> IntoIterator for RawTable<T, S, A> {
type Item = T;
type IntoIter = IntoIter<T, S, A>;
fn into_iter(self) -> Self::IntoIter {
let this = ManuallyDrop::new(self);
IntoIter {
iter: unsafe { core::ptr::read(&this.data) }
.into_vec()
.into_iter(),
len: this.len,
phantom: PhantomData,
}
}
}
impl<'a, T, S: Status> Iterator for Iter<'a, T, S> {
type Item = &'a T;
#[inline]
fn next(&mut self) -> Option<&'a T> {
if self.len == 0 {
return None;
}
loop {
let next = self.iter.next();
debug_assert!(next.is_some());
let slot = unsafe { next.unwrap_unchecked() };
if slot.status.is_hash() {
self.len -= 1;
return Some(unsafe { slot.data.assume_init_ref() });
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl<T, S: Status> ExactSizeIterator for Iter<'_, T, S> {
#[inline]
fn len(&self) -> usize {
self.len
}
}
impl<T, S: Status> FusedIterator for Iter<'_, T, S> {}
impl<'a, T, S: Status> Iterator for IterMut<'a, T, S> {
type Item = &'a mut T;
#[inline]
fn next(&mut self) -> Option<&'a mut T> {
if self.len == 0 {
return None;
}
loop {
let next = self.iter.next();
debug_assert!(next.is_some());
let slot = unsafe { next.unwrap_unchecked() };
if slot.status.is_hash() {
self.len -= 1;
return Some(unsafe { slot.data.assume_init_mut() });
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl<T, S: Status> ExactSizeIterator for IterMut<'_, T, S> {
#[inline]
fn len(&self) -> usize {
self.len
}
}
impl<T, S: Status> FusedIterator for IterMut<'_, T, S> {}
impl<T, S: Status, A: Allocator> Iterator for IntoIter<T, S, A> {
type Item = T;
#[inline]
fn next(&mut self) -> Option<T> {
if self.len == 0 {
return None;
}
loop {
let next = self.iter.next();
debug_assert!(next.is_some());
let slot = unsafe { next.unwrap_unchecked() };
if slot.status.is_hash() {
self.len -= 1;
return Some(unsafe { slot.data.assume_init() });
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl<T, S: Status, A: Allocator> ExactSizeIterator for IntoIter<T, S, A> {
#[inline]
fn len(&self) -> usize {
self.len
}
}
impl<T, S: Status, A: Allocator> FusedIterator for IntoIter<T, S, A> {}
impl<T, S: Status, A: Allocator> Drop for IntoIter<T, S, A> {
fn drop(&mut self) {
while self.len != 0 {
let next = self.iter.next();
debug_assert!(next.is_some());
let mut slot = unsafe { next.unwrap_unchecked() };
if slot.status.is_hash() {
self.len -= 1;
unsafe { slot.data.assume_init_drop() };
}
}
}
}
impl<T, S: Status> Iterator for Drain<'_, T, S> {
type Item = T;
#[inline]
fn next(&mut self) -> Option<T> {
if self.len == 0 {
return None;
}
loop {
let next = self.iter.next();
debug_assert!(next.is_some());
let slot = unsafe { next.unwrap_unchecked() };
let status = slot.status;
slot.status = S::FREE;
if status.is_hash() {
self.len -= 1;
return Some(unsafe { slot.data.assume_init_read() });
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl<T, S: Status> ExactSizeIterator for Drain<'_, T, S> {
#[inline]
fn len(&self) -> usize {
self.len
}
}
impl<T, S: Status> FusedIterator for Drain<'_, T, S> {}
impl<T, S: Status> Drop for Drain<'_, T, S> {
fn drop(&mut self) {
while self.len != 0 {
let next = self.iter.next();
debug_assert!(next.is_some());
let slot = unsafe { next.unwrap_unchecked() };
let status = slot.status;
slot.status = S::FREE;
if status.is_hash() {
self.len -= 1;
unsafe { slot.data.assume_init_drop() };
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn new_cap_len_0() {
let table = RawTable::<u32, u32>::new();
assert_eq!(table.capacity(), 0);
assert_eq!(table.len(), 0);
}
#[test]
fn insert_get_iter() {
let mut table = RawTable::<u32, u32>::new();
let numbers = [1, 4, 0, 5, 14, 4];
let sorted = [0, 1, 4, 5, 14];
for number in numbers {
match table.find_or_find_insert_slot(number as u64, |&x| x == number) {
Ok(_) => assert_eq!(number, 4),
Err(slot) => unsafe {
table.insert_in_slot_unchecked(number as u64, slot, number);
},
}
}
assert_eq!(table.len(), 5);
assert!(table.capacity() >= 5);
for mut number in numbers {
assert_eq!(table.get(number as u64, |&x| x == number), Some(&number));
assert_eq!(
table.get_mut(number as u64, |&x| x == number),
Some(&mut number)
);
}
let iter = table.iter();
assert_eq!(iter.len(), 5);
let mut res: Vec<u32> = iter.copied().collect();
res.sort();
assert_eq!(&res[..], &sorted[..]);
let iter = table.iter_mut();
assert_eq!(iter.len(), 5);
let mut res: Vec<u32> = iter.map(|&mut x| x).collect();
res.sort();
assert_eq!(&res[..], &sorted[..]);
let iter = table.into_iter();
assert_eq!(iter.len(), 5);
let mut res: Vec<u32> = iter.collect();
res.sort();
assert_eq!(&res[..], &sorted[..]);
}
#[test]
fn retain_drain() {
let mut table = RawTable::<u32, u32>::new();
let numbers = [1, 2, 3, 4, 5, 6, 7];
for number in numbers {
match table.find_or_find_insert_slot(number as u64, |&x| x == number) {
Ok(_) => unreachable!(),
Err(slot) => unsafe {
table.insert_in_slot_unchecked(number as u64, slot, number);
},
}
}
assert_eq!(table.len(), 7);
table.retain(
|&mut x| x.is_multiple_of(2),
|x| assert!(!x.is_multiple_of(2)),
);
assert_eq!(table.len(), 3);
let iter = table.drain();
assert_eq!(iter.len(), 3);
let mut res: Vec<u32> = iter.collect();
res.sort();
assert_eq!(&res[..], &[2, 4, 6]);
assert_eq!(table.len(), 0);
}
#[test]
fn remove() {
let mut table = RawTable::<u32, u32>::new();
let numbers = [4, 0, 2];
for number in numbers {
match table.find_or_find_insert_slot(number as u64, |&x| x == number) {
Ok(_) => unreachable!(),
Err(slot) => unsafe {
table.insert_in_slot_unchecked(number as u64, slot, number);
},
}
}
assert_eq!(table.len(), 3);
assert_eq!(table.remove_entry(0, |&x| x == 0), Some(0));
assert_eq!(table.len(), 2);
assert_eq!(table.remove_entry(0, |&x| x == 0), None);
assert_eq!(table.len(), 2);
}
}