#[cfg(feature = "_docs_examples")]
define_static_map! {
#[doc = crate::_tags!(example data_structure)]
pub const MapStaticConstU8Example, KEY: u8
}
#[cfg(feature = "_docs_examples")]
define_static_map! {
#[doc = crate::_tags!(example data_structure)]
pub MapStaticU16Example, KEY: u16
}
#[cfg(feature = "_docs_examples")]
define_static_map! {
#[doc = crate::_tags!(example data_structure)]
pub typeid MapStaticTypeIdExample
}
#[doc = crate::_tags!(construction data_structure)]
#[doc = crate::_doc_location!("data/id")]
#[doc = concat!["| **Const** | `define_static_map![const MyMap, KEY: u16]` |",
"Generates a fully `const` hashmap with compile-time operations and const methods. |"]]
#[doc = concat!["| **Runtime** | `define_static_map![MyMap, KEY: u16]` |",
"Generates a non-const variant storing markers as struct fields, suitable for runtime mutation. |"]]
#[doc = concat!["| **TypeId-based** | `define_static_map![typeid MyMap]` |",
"Uses `TypeId` hashes as keys and provides type-oriented helper methods. |"]]
#[cfg_attr(cargo_primary_package, doc(hidden))]
#[macro_export]
macro_rules! define_static_map {
(
// Const variant
// ----------------------------------------------------------------------------------------
// Default constructor:
$(#[$attr:meta])*
$vis:vis const $NAME:ident, KEY:$KEY:ty $(,)?
) => {
$crate::define_static_map![
$(#[$attr])*
$vis const $NAME, KEY:$KEY,
EMPTY:<$KEY>::MIN, TOMB:<$KEY>::MAX,
HASHER:|bytes| $crate::HasherFx::<usize>::hash_bytes(bytes)
];
};
(// Custom Empty/Tomb, Default Hasher:
$(#[$attr:meta])*
$vis:vis const $NAME:ident, KEY:$KEY:ty,
EMPTY:$EMPTY:expr, TOMB:$TOMB:expr $(,)?
) => {
$crate::define_static_map![
$(#[$attr])*
$vis const $NAME, KEY:$KEY,
EMPTY:$EMPTY, TOMB:$TOMB,
HASHER:|bytes| $crate::HasherFx::<usize>::hash_bytes(bytes)
];
};
(// Custom Hasher, Default Empty/Tomb:
$(#[$attr:meta])*
$vis:vis const $NAME:ident, KEY:$KEY:ty,
HASHER: | $HASH_ARG:ident | $HASH_EXPR:expr $(,)?
) => {
$crate::define_static_map![
$(#[$attr])*
$vis const $NAME, KEY:$KEY,
EMPTY:<$KEY>::MIN, TOMB:<$KEY>::MAX,
HASHER: | $HASH_ARG | $HASH_EXPR
];
};
(// Fully customizable:
$(#[$attr:meta])*
$vis:vis const $NAME:ident, KEY:$KEY:ty,
EMPTY:$EMPTY:expr, TOMB:$TOMB:expr,
HASHER: | $HASH_ARG:ident | $HASH_EXPR:expr $(,)?
) => {
$(#[$attr])*
#[doc = concat!("A fully `const` static hashmap with compile-time `",
stringify!($KEY), "` keys.")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
$vis struct $NAME<K: Copy, V, const N: usize> {
keys: [K; N],
values: [V; N],
}
$crate::define_static_map![%shared $NAME, KEY:$KEY,
HASHER:|bytes| $crate::HasherFx::<usize>::hash_bytes(bytes)
];
#[allow(unused)]
impl<V, const N: usize> $NAME<$KEY, V, N> {
pub const EMPTY: $KEY = $EMPTY as $KEY;
pub const TOMB: $KEY = $TOMB as $KEY;
pub const fn empty(&self) -> $KEY { $EMPTY }
pub const fn tomb(&self) -> $KEY { $TOMB }
}
impl<V: Copy + $crate::ConstInit, const N: usize>
$crate::ConstInit for $NAME<$KEY, V, N> {
const INIT: Self = Self::new();
}
impl<V: Default, const N: usize> Default for $NAME<$KEY, V, N> {
#[allow(unexpected_cfgs, reason = "init_array")]
fn default() -> Self {
Self:: debug_assert_invariants();
Self {
keys: [$EMPTY; N],
values: $crate::init_array![default [V; N], "safe_data", "unsafe_array"],
}
}
}
#[allow(unused)]
impl<V: Copy + $crate::ConstInit, const N: usize> $NAME<$KEY, V, N> {
#[allow(clippy::float_cmp_const)]
pub const fn new() -> Self {
Self:: debug_assert_invariants();
Self {
keys: [$EMPTY; N],
values: [V::INIT; N],
}
}
}
#[allow(unused)]
impl<V, const N: usize> $NAME<$KEY, V, N> {
pub const fn get_ref(&self, key: $KEY) -> Option<&V> {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
while i < N {
if self.keys[index] == key { return Some(&self.values[index]); }
if self.keys[index] == self.empty() { return None; }
index = (index + 1) % N;
i += 1;
}
None
}
pub const fn get_mut(&mut self, key: $KEY) -> Option<&mut V> {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
while i < N {
if self.keys[index] == key { return Some(&mut self.values[index]); }
if self.keys[index] == self.empty() { return None; }
index = (index + 1) % N;
i += 1;
}
None
}
pub const fn entry(&mut self, key: $KEY) -> $crate::StaticMapEntry<'_, V> {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
let mut tombstone_index = None;
while i < N {
if self.keys[index] == self.empty() {
return $crate::StaticMapEntry::Vacant(index);
}
if self.keys[index] == key {
return $crate::StaticMapEntry::Occupied(&mut self.values[index]);
}
if self.keys[index] == self.tomb() && tombstone_index.is_none() {
tombstone_index = Some(index);
}
index = (index + 1) % N;
i += 1;
}
$crate::StaticMapEntry::Vacant($crate::unwrap![some_or tombstone_index, N])
}
pub const fn len(&self) -> usize {
let mut count = 0;
let mut i = 0;
while i < N {
if self.keys[i] != self.empty() && self.keys[i] != self.tomb() { count += 1; }
i += 1;
}
count
}
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
pub const fn is_full(&self) -> bool {
self.len() == N
}
pub const fn should_rebuild(&self) -> bool {
self.deleted_count() >= N / 2
}
pub const fn deleted_count(&self) -> usize {
let mut count = 0;
let mut i = 0;
while i < N {
if self.keys[i] == self.tomb() { count += 1; }
i += 1;
}
count
}
pub const fn load_factor(&self) -> f32 {
self.len() as f32 / N as f32
}
const fn debug_assert_valid_key(key: $KEY) {
debug_assert!(key != $EMPTY, "Key cannot be `EMPTY` marker");
debug_assert!(key != $TOMB, "Key cannot be `TOMB` marker");
}
const fn debug_assert_invariants() {
debug_assert![$EMPTY != $TOMB, "`$EMPTY` and `$TOMB` must be distinct"];
debug_assert![($EMPTY as i128) >= (<$KEY>::MIN as i128)
&& ($EMPTY as i128) <= (<$KEY>::MAX as i128),
"`$EMPTY` value is out of range for type `$KEY`"];
debug_assert![($TOMB as i128) >= (<$KEY>::MIN as i128)
&& ($TOMB as i128) <= (<$KEY>::MAX as i128),
"`$TOMB` value is out of range for type `$KEY`"];
}
}
#[allow(unused)]
impl<V: Copy, const N: usize> $NAME<$KEY, V, N> {
#[allow(clippy::float_cmp, clippy::float_cmp_const)]
pub const fn insert(&mut self, key: $KEY, value: V)
-> Result<(), $crate::NotEnoughSpace> {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
let mut tombstone_index = None;
while i < N {
if self.keys[index] == self.empty() || self.keys[index] == self.tomb() {
let slot = if let Some(tomb) = tombstone_index { tomb } else { index };
self.keys[slot] = key;
self.values[slot] = value;
return Ok(());
}
if self.keys[index] == key {
self.values[index] = value; return Ok(());
}
if self.keys[index] == self.tomb() && tombstone_index.is_none() {
tombstone_index = Some(index);
}
index = (index + 1) % N;
i += 1;
}
Err($crate::NotEnoughSpace(Some(1)))
}
#[allow(clippy::float_cmp, clippy::float_cmp_const)]
pub const fn get(&self, key: $KEY) -> Option<V> {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
while i < N {
if self.keys[index] == key { return Some(self.values[index]); }
if self.keys[index] == self.empty() { return None; } index = (index + 1) % N;
i += 1;
}
None
}
}
#[allow(unused)]
impl<V: Copy + $crate::ConstInit, const N: usize> $NAME<$KEY, V, N> {
#[allow(clippy::float_cmp, clippy::float_cmp_const)]
pub const fn remove(&mut self, key: $KEY) -> bool {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
while i < N {
if self.keys[index] == key { self.keys[index] = self.tomb(); return true; }
if self.keys[index] == self.empty() { return false; }
index = (index + 1) % N;
i += 1;
}
false
}
pub const fn remove_rebuild(&mut self, key: $KEY) -> bool {
let removed = self.remove(key);
if removed && self.should_rebuild() { self.rebuild(); }
removed
}
pub const fn rebuild(&mut self) { *self = self.rebuilt(); }
pub const fn rebuilt(&self) -> Self {
let mut new_table = Self::new();
let mut i = 0;
while i < N {
if self.keys[i] != self.empty() && self.keys[i] != self.tomb() {
let _ = new_table.insert(self.keys[i], self.values[i]);
}
i += 1;
}
new_table
}
}
};
(
// Runtime variant
// ----------------------------------------------------------------------------------------
// Default constructor:
$(#[$attr:meta])*
$vis:vis $NAME:ident, KEY:$KEY:ty $(,)?
) => {
$crate::define_static_map![
$(#[$attr])*
$vis $NAME, KEY:$KEY, EMPTY:<$KEY>::MIN, TOMB:<$KEY>::MAX,
HASHER:|bytes| $crate::HasherFx::<usize>::hash_bytes(bytes)
];
};
(// Custom Empty/Tomb, Default Hasher:
$(#[$attr:meta])*
$vis:vis $NAME:ident, KEY:$KEY:ty,
EMPTY:$EMPTY:expr, TOMB:$TOMB:expr $(,)?
) => {
$crate::define_static_map![
$(#[$attr])*
$vis $NAME, KEY:$KEY, EMPTY:$EMPTY, TOMB:$TOMB,
HASHER:|bytes| $crate::HasherFx::<usize>::hash_bytes(bytes)
];
};
(// Custom Hasher, Default Empty/Tomb:
$(#[$attr:meta])*
$vis:vis $NAME:ident, KEY:$KEY:ty,
HASHER: | $HASH_ARG:ident | $HASH_EXPR:expr $(,)?
) => {
$crate::define_static_map![
$(#[$attr])*
$vis $NAME, KEY:$KEY,
EMPTY:<$KEY>::MIN, TOMB:<$KEY>::MAX,
HASHER: | $HASH_ARG | $HASH_EXPR
];
};
(// Fully customizable:
$(#[$attr:meta])*
$vis:vis $NAME:ident, KEY:$KEY:ty,
EMPTY:$EMPTY:expr, TOMB:$TOMB:expr,
HASHER: | $HASH_ARG:ident | $HASH_EXPR:expr $(,)?
) => {
$(#[$attr])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
$vis struct $NAME<K: Copy, V, const N: usize> {
keys: [K; N],
values: [V; N],
empty: K,
tomb: K,
}
$crate::define_static_map![%shared $NAME, KEY:$KEY,
HASHER:|bytes| $crate::HasherFx::<usize>::hash_bytes(bytes)
];
#[allow(unused)]
impl<V, const N: usize> $NAME<$KEY, V, N> {
pub fn empty(&self) -> $KEY { self.empty }
pub fn tomb(&self) -> $KEY { self.tomb }
}
impl<V: Default, const N: usize> Default for $NAME<$KEY, V, N> {
#[allow(unexpected_cfgs, reason = "init_array")]
fn default() -> Self {
Self:: debug_assert_invariants();
Self {
keys: [$EMPTY; N],
values: $crate::init_array![default [V; N], "safe_data", "unsafe_array"],
empty: $EMPTY,
tomb: $TOMB,
}
}
}
#[allow(unused)]
impl<V, const N: usize> $NAME<$KEY, V, N> {
pub fn new() -> Self where V: Default {
Self::default()
}
#[allow(unexpected_cfgs, reason = "init_array")]
fn new_cloned(value: V) -> Self where V: Clone {
Self:: debug_assert_invariants();
Self {
keys: [$EMPTY; N],
values: $crate::init_array![clone [V; N], "safe_data", "unsafe_array", value],
empty: $EMPTY,
tomb: $TOMB,
}
}
pub fn get_ref(&self, key: $KEY) -> Option<&V> {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
while i < N {
if self.keys[index] == key { return Some(&self.values[index]); }
if self.keys[index] == self.empty() { return None; }
index = (index + 1) % N;
i += 1;
}
None
}
pub fn get_mut(&mut self, key: $KEY) -> Option<&mut V> {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
while i < N {
if self.keys[index] == key { return Some(&mut self.values[index]); }
if self.keys[index] == self.empty() { return None; }
index = (index + 1) % N;
i += 1;
}
None
}
pub fn entry(&mut self, key: $KEY) -> $crate::StaticMapEntry<'_, V> {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
let mut tombstone_index = None;
while i < N {
if self.keys[index] == self.empty() {
return $crate::StaticMapEntry::Vacant(index);
}
if self.keys[index] == key {
return $crate::StaticMapEntry::Occupied(&mut self.values[index]);
}
if self.keys[index] == self.tomb() && tombstone_index.is_none() {
tombstone_index = Some(index);
}
index = (index + 1) % N;
i += 1;
}
$crate::StaticMapEntry::Vacant($crate::unwrap![some_or tombstone_index, N])
}
pub fn len(&self) -> usize {
let mut count = 0;
let mut i = 0;
while i < N {
if self.keys[i] != self.empty() && self.keys[i] != self.tomb() { count += 1; }
i += 1;
}
count
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn is_full(&self) -> bool {
self.len() == N
}
pub fn should_rebuild(&self) -> bool {
self.deleted_count() >= N / 2
}
pub fn deleted_count(&self) -> usize {
let mut count = 0;
let mut i = 0;
while i < N {
if self.keys[i] == self.tomb() { count += 1; }
i += 1;
}
count
}
pub fn load_factor(&self) -> f32 {
self.len() as f32 / N as f32
}
fn debug_assert_valid_key(key: $KEY) {
debug_assert!(key != $EMPTY, "Key cannot be `EMPTY` marker");
debug_assert!(key != $TOMB, "Key cannot be `TOMB` marker");
}
fn debug_assert_invariants() {
debug_assert![$EMPTY != $TOMB, "`$EMPTY` and `$TOMB` must be distinct"];
debug_assert![($EMPTY as i128) >= (<$KEY>::MIN as i128)
&& ($EMPTY as i128) <= (<$KEY>::MAX as i128),
"`$EMPTY` value is out of range for type `$KEY`"];
debug_assert![($TOMB as i128) >= (<$KEY>::MIN as i128)
&& ($TOMB as i128) <= (<$KEY>::MAX as i128),
"`$TOMB` value is out of range for type `$KEY`"];
}
#[allow(clippy::float_cmp, clippy::float_cmp_const)]
pub fn insert(&mut self, key: $KEY, value: V)
-> Result<(), $crate::NotEnoughSpace> {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
let mut tombstone_index = None;
while i < N {
if self.keys[index] == self.empty() || self.keys[index] == self.tomb() {
let slot = if let Some(tomb) = tombstone_index { tomb } else { index };
self.keys[slot] = key;
self.values[slot] = value;
return Ok(());
}
if self.keys[index] == key {
self.values[index] = value; return Ok(());
}
if self.keys[index] == self.tomb() && tombstone_index.is_none() {
tombstone_index = Some(index);
}
index = (index + 1) % N;
i += 1;
}
Err($crate::NotEnoughSpace(Some(1)))
}
}
#[allow(unused)]
impl<V: Copy, const N: usize> $NAME<$KEY, V, N> {
#[allow(clippy::float_cmp, clippy::float_cmp_const)]
pub fn get(&self, key: $KEY) -> Option<V> {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
while i < N {
if self.keys[index] == key { return Some(self.values[index]); }
if self.keys[index] == self.empty() { return None; } index = (index + 1) % N;
i += 1;
}
None
}
#[allow(clippy::float_cmp, clippy::float_cmp_const)]
pub fn remove(&mut self, key: $KEY) -> bool {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
while i < N {
if self.keys[index] == key { self.keys[index] = self.tomb(); return true; }
if self.keys[index] == self.empty() { return false; }
index = (index + 1) % N;
i += 1;
}
false
}
}
#[allow(unused)]
impl<V: Copy + Default, const N: usize> $NAME<$KEY, V, N> {
pub fn remove_rebuild(&mut self, key: $KEY) -> bool {
let removed = self.remove(key);
if removed && self.should_rebuild() { self.rebuild(); }
removed
}
pub fn rebuild(&mut self) { *self = self.rebuilt(); }
pub fn rebuilt(&self) -> Self {
let mut new_table = Self::new();
let mut i = 0;
while i < N {
if self.keys[i] != self.empty() && self.keys[i] != self.tomb() {
let _ = new_table.insert(self.keys[i], self.values[i]);
}
i += 1;
}
new_table
}
}
};
(
// TypeId runtime variant
// ----------------------------------------------------------------------------------------
// Uses 64-bit hashes of `TypeId`s for the keys:
$(#[$attr:meta])*
$vis:vis typeid $NAME:ident $(,)?) => {
$crate::define_static_map![
$(#[$attr])*
#[doc = "A `TypeId`-keyed static hashmap.\n\n\
This variant uses 64-bit hashes of Rust `TypeId`s as keys and adds \
type-oriented methods such as `insert_type`, `get_type`, and `remove_type`. \
It is built on the runtime hashmap variant, inheriting its stored `empty` \
and `tomb` markers and behavior.\n\n"]
$vis $NAME, KEY: u64,
EMPTY: type_id_hash::<Empty>(), TOMB: type_id_hash::<Tomb>(),
HASHER: |bytes| $crate::HasherFx::<usize>::hash_bytes(bytes)
];
struct Empty;
struct Tomb;
fn type_id_hash<T: 'static>() -> u64 {
let mut hasher = $crate::HasherFx::<u64>::new();
let id = $crate::TypeId::of::<T>();
$crate::Hash::hash(&id, &mut hasher);
$crate::Hasher::finish(&hasher)
}
#[allow(unused)]
impl<V, const N: usize> $NAME<u64, V, N> {
pub fn type_id_hash<T: 'static>() -> u64 { type_id_hash::<T>() }
pub fn get_ref_type<T: 'static>(&self) -> Option<&V> {
let key = Self::type_id_hash::<T>();
self.get_ref(key)
}
pub fn get_mut_type<T: 'static>(&mut self) -> Option<&mut V> {
let key = Self::type_id_hash::<T>();
self.get_mut(key)
}
pub fn insert_type<T: 'static>(&mut self, value: V)
-> Result<(), $crate::NotEnoughSpace> {
let key = Self::type_id_hash::<T>();
self.insert(key, value)
}
}
#[allow(unused)]
impl<V: Copy, const N: usize> $NAME<u64, V, N> {
pub fn get_type<T: 'static>(&self) -> Option<V> {
let key = Self::type_id_hash::<T>();
self.get(key)
}
pub fn remove_type<T: 'static>(&mut self) -> bool {
let key = Self::type_id_hash::<T>();
self.remove(key)
}
}
};
(
// shared internal functionality
// ----------------------------------------------------------------------------------------
%shared $NAME:ident, KEY:$KEY:ty,
HASHER: | $HASH_ARG:ident | $HASH_EXPR:expr $(,)?) => {
#[allow(unused)]
impl<V, const N: usize> $NAME<$KEY, V, N> {
pub fn insert_move(&mut self, key: $KEY, value: V)
-> Result<(), $crate::NotEnoughSpace> {
match self.entry(key) {
$crate::StaticMapEntry::Occupied(slot) => {
*slot = value; Ok(())
}
$crate::StaticMapEntry::Vacant(index) if index < N => {
self.keys[index] = key;
self.values[index] = value;
Ok(())
}
_ => Err($crate::NotEnoughSpace(Some(1))),
}
}
#[rustfmt::skip]
pub fn replace(&mut self, key: $KEY, replacement: V) -> Option<V> {
match self._replace_internal(key) {
Some(slot) => Some($crate::Mem::replace(slot, replacement)),
None => None,
}
}
#[rustfmt::skip]
pub fn replace_default(&mut self, key: $KEY) -> Option<V> where V: Default {
self._replace_internal(key).map(|v| $crate::Mem::replace(v, V::default()))
}
#[rustfmt::skip]
pub fn replace_with<F>(&mut self, key: $KEY, replacement: F) -> Option<V>
where F: FnOnce() -> V {
self._replace_internal(key).map(|v| $crate::Mem::replace(v, replacement()))
}
fn _replace_internal(&mut self, key: $KEY) -> Option<&mut V> {
Self::debug_assert_valid_key(key);
let mut index = self.hash_index(key);
let mut i = 0;
while i < N {
if self.keys[index] == key {
self.keys[index] = self.tomb();
return Some(&mut self.values[index]);
}
if self.keys[index] == self.empty() { return None; }
index = (index + 1) % N;
i += 1;
}
None
}
pub const fn capacity(&self) -> usize {
N
}
#[$crate::compile(not(same($KEY, char)))] pub const fn hash_index(&self, key: $KEY) -> usize {
let $HASH_ARG = &key.to_le_bytes();
let expr = $HASH_EXPR;
expr % N
}
#[$crate::compile(same($KEY, char))] pub const fn hash_index(&self, key: $KEY) -> usize {
let $HASH_ARG = &(key as u32).to_le_bytes();
let expr = $HASH_EXPR;
expr % N
}
}
};
}
#[doc(inline)]
pub use define_static_map;