Skip to main content

kevy_map/
slot.rs

1//! Slot-index access: probe once, then reach the same entry again without
2//! hashing.
3//!
4//! A slot index is only a position. Any insert or remove may move, free or
5//! reuse it, so every access re-checks that the slot is occupied and hands
6//! back the key stored there; a caller that keeps an index across
7//! mutations compares that key before trusting the value. Nothing here can
8//! read an uninitialised slot.
9
10use core::borrow::Borrow;
11
12use kevy_hash::KevyHash;
13
14use crate::map::KevyMap;
15
16impl<K, V> KevyMap<K, V> {
17    /// The slot `key` occupies, or `None` if it is absent. One probe; the
18    /// index stays valid until the next insert or remove.
19    ///
20    /// ```
21    /// let mut m = kevy_map::KevyMap::new();
22    /// m.insert(7u64, "seven");
23    /// let slot = m.find_slot(&7).ok_or("present")?;
24    /// assert_eq!(m.slot(slot), Some((&7, &"seven")));
25    /// assert_eq!(m.find_slot(&8), None);
26    /// # Ok::<(), &str>(())
27    /// ```
28    #[inline]
29    #[must_use]
30    pub fn find_slot<Q>(&self, key: &Q) -> Option<usize>
31    where
32        K: Borrow<Q>,
33        Q: KevyHash + Eq + ?Sized,
34    {
35        self.find_by_borrow(key)
36    }
37
38    /// The key and value at `slot`, or `None` when the slot is out of range
39    /// or not occupied. No hashing; the returned key says whose entry it is.
40    ///
41    /// ```
42    /// let mut m = kevy_map::KevyMap::new();
43    /// m.insert(1u64, 10u32);
44    /// let slot = m.find_slot(&1).ok_or("present")?;
45    /// m.remove(&1);
46    /// assert_eq!(m.slot(slot), None, "a freed slot reads as empty");
47    /// assert_eq!(m.slot(usize::MAX), None);
48    /// # Ok::<(), &str>(())
49    /// ```
50    #[inline]
51    #[must_use]
52    pub fn slot(&self, slot: usize) -> Option<(&K, &V)> {
53        if !self.slot_is_full(slot) {
54            return None;
55        }
56        // SAFETY: `slot_is_full` checked `slot < cap` and that its metadata
57        // byte marks an initialised entry.
58        let kv = unsafe { (*self.slots_ptr.as_ptr().add(slot)).assume_init_ref() };
59        Some((&kv.0, &kv.1))
60    }
61
62    /// Mutable [`slot`](Self::slot): the key and a mutable value at `slot`,
63    /// or `None` when the slot is out of range or not occupied.
64    ///
65    /// ```
66    /// let mut m = kevy_map::KevyMap::new();
67    /// m.insert(1u64, 10u32);
68    /// let slot = m.find_slot(&1).ok_or("present")?;
69    /// if let Some((&1, v)) = m.slot_mut(slot) {
70    ///     *v += 1;
71    /// }
72    /// assert_eq!(m.get(&1), Some(&11));
73    /// # Ok::<(), &str>(())
74    /// ```
75    #[inline]
76    #[must_use]
77    pub fn slot_mut(&mut self, slot: usize) -> Option<(&K, &mut V)> {
78        if !self.slot_is_full(slot) {
79            return None;
80        }
81        // SAFETY: as in `slot`.
82        let kv = unsafe { (*self.slots_ptr.as_ptr().add(slot)).assume_init_mut() };
83        Some((&kv.0, &mut kv.1))
84    }
85
86    #[inline]
87    fn slot_is_full(&self, slot: usize) -> bool {
88        // SAFETY: read only after the bound check; the metadata array holds
89        // at least `cap` bytes.
90        slot < self.cap && unsafe { *self.metadata_ptr.as_ptr().add(slot) } & 0x80 == 0
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use crate::KevyMap;
97
98    #[test]
99    fn a_slot_follows_its_entry_until_the_table_changes_shape() {
100        let mut m: KevyMap<u64, u64> = KevyMap::new();
101        assert_eq!(m.slot(0), None, "an unallocated table has no slots");
102        for k in 0..crate::scaled(1000) as u64 {
103            m.insert(k, k * 2);
104        }
105        for k in 0..crate::scaled(1000) as u64 {
106            let s = m.find_slot(&k).expect("present");
107            assert_eq!(m.slot(s), Some((&k, &(k * 2))));
108            *m.slot_mut(s).expect("full").1 += 1;
109            assert_eq!(m.get(&k), Some(&(k * 2 + 1)));
110        }
111        let s = m.find_slot(&5).expect("present");
112        m.remove(&5);
113        assert_eq!(m.slot(s), None);
114        assert!(m.slot_mut(s).is_none());
115        let full = (0..m.capacity()).filter(|&i| m.slot(i).is_some()).count();
116        assert_eq!(full, m.len(), "exactly the occupied slots read as full");
117    }
118}