overlay_map/
lib.rs

1//! A two-layered map structure where the foreground is mutable and the
2//! background is preserved.
3//!
4//! OverlayMap lets you insert values without cloning, while keeping a single
5//! layer of historical context. Each key has a current value (foreground) and
6//! may have a previous value (background), which is automatically managed
7//! during updates.
8//!
9//! ```rust
10//! use overlay_map::OverlayMap;
11//!
12//! #[derive(Debug, Clone, PartialEq)]
13//! struct QuantumBit {
14//!     collapsed: bool,
15//!     value: Option<bool>,
16//! }
17//!
18//! /// Simulates measurement collapse
19//! fn collapse(mut qbit: QuantumBit) -> QuantumBit {
20//!     qbit.collapsed = true;
21//!     qbit.value = Some(rand::random());
22//!     qbit
23//! }
24//!
25//! fn main() {
26//!     let mut qstate = OverlayMap::<&str, QuantumBit>::new();
27//!
28//!     // Push an uncollapsed qubit
29//!     qstate.push(
30//!         "qbit_1",
31//!         QuantumBit {
32//!             collapsed: false,
33//!             value: None,
34//!         },
35//!     );
36//!
37//!     // Observe the qubit: only collapse if it's not already
38//!     let did_observe = qstate.push_if(&"qbit_1", |current| {
39//!         if current.collapsed {
40//!             None // already collapsed, don't change
41//!         } else {
42//!             Some(collapse(current.clone())) // clone *only* if needed
43//!         }
44//!     });
45//!
46//!     println!("Was observed?       {}", did_observe);
47//!     println!("After observation:  {:?}", qstate.fg(&"qbit_1"));
48//!     println!("Before observation: {:?}", qstate.bg(&"qbit_1"));
49//! }
50//! ```
51
52use std::{
53    collections::{HashMap, hash_map::RandomState},
54    hash::{BuildHasher, Hash},
55    mem::MaybeUninit,
56};
57
58/// A two-layered map where each key has a mutable foreground and an optional
59/// background value.
60///
61/// When inserting a new value for a key, the previous value (if any) is
62/// automatically moved to the background. Background values are preserved but
63/// not cloned.
64///
65/// This map is not thread-safe for mutation. It may be shared across threads
66/// for read-only access.
67#[derive(Debug, Default)]
68pub struct OverlayMap<K, V, S = RandomState>
69where
70    K: Eq + Hash,
71{
72    map: HashMap<K, Entry<V>, S>,
73}
74
75unsafe impl<K, V, S> Sync for OverlayMap<K, V, S>
76where
77    K: Eq + Hash + Sync,
78    S: Sync,
79{
80}
81
82impl<K, V, S> OverlayMap<K, V, S>
83where
84    K: Eq + Hash,
85    S: BuildHasher + Default,
86{
87    /// Creates a new, empty `OverlayMap` with the default hasher.
88    pub fn new() -> Self {
89        Self::with_hasher(Default::default())
90    }
91
92    /// Creates an empty `OverlayMap` with the specified capacity and default hasher.
93    pub fn with_capacity(capacity: usize) -> Self {
94        Self::with_capacity_and_hasher(capacity, Default::default())
95    }
96
97    /// Creates an empty `OverlayMap` that will use the given hasher.
98    pub fn with_hasher(hasher: S) -> Self {
99        Self {
100            map: HashMap::with_hasher(hasher),
101        }
102    }
103
104    /// Creates an empty `OverlayMap` with the specified capacity and hasher.
105    pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self {
106        Self {
107            map: HashMap::with_capacity_and_hasher(capacity, hasher),
108        }
109    }
110
111    /// Number of unique keys in the map.
112    pub fn len(&self) -> usize {
113        self.map.len()
114    }
115
116    /// Check if the map is empty.
117    pub fn is_empty(&self) -> bool {
118        self.map.is_empty()
119    }
120
121    /// Get an immutable reference to the value associated with the key.
122    ///
123    /// Returns `None` if the key was not found in the map.
124    pub fn fg(&self, key: &K) -> Option<&V> {
125        self.map.get(key).map(|entry| entry.fg())
126    }
127
128    /// Get an immutable reference to the value associated with the key in the background layer.
129    ///
130    /// Returns `None` if the key was not found in the background layer.
131    pub fn bg(&self, key: &K) -> Option<&V> {
132        self.map.get(key).and_then(|entry| entry.bg())
133    }
134
135    /// Push a value into the foreground layer, preserving the previous value in
136    /// the background.
137    ///
138    /// If the key was already present, the current foreground is moved to the
139    /// background slot, and the new value becomes the new foreground. No
140    /// cloning occurs. The old background value is dropped if it was present.
141    ///
142    /// Returns `true` if there was already a foreground value (i.e. a
143    /// background now exists).
144    pub fn push(&mut self, key: K, value: V) -> bool {
145        if let Some(entry) = self.map.get_mut(&key) {
146            entry.push(value);
147            true
148        } else {
149            self.map.insert(key, Entry::new(value));
150            false
151        }
152    }
153
154    /// Conditionally push a new value into the foreground based on the current
155    /// value.
156    ///
157    /// If the key exists, the current foreground value is passed to the
158    /// predicate. If the predicate returns `Some(new_val)`, the new value is
159    /// pushed and the old one is preserved in the background. If it returns
160    /// `None`, nothing is changed.
161    ///
162    /// Returns `true` if a new value was pushed.
163    pub fn push_if<F>(&mut self, key: &K, predicate: F) -> bool
164    where
165        F: FnOnce(&V) -> Option<V>,
166    {
167        let entry = match self.map.get_mut(key) {
168            Some(e) => e,
169            None => return false,
170        };
171
172        match predicate(entry.fg()) {
173            Some(new) => {
174                entry.push(new);
175                true
176            }
177            None => false,
178        }
179    }
180
181    /// Overlay multiple values onto the map.
182    ///
183    /// Each key-value pair is pushed into the foreground layer. If the key was
184    /// already present, the existing foreground value is moved to the
185    /// background. This does not clone or retain old values beyond the
186    /// background layer.
187    ///
188    /// Returns the number of keys that already existed (i.e. pushes that
189    /// replaced a foreground).
190    pub fn overlay<I>(&mut self, iter: I) -> usize
191    where
192        I: IntoIterator<Item = (K, V)>,
193    {
194        let mut replaced = 0;
195
196        for (key, val) in iter {
197            replaced += self.push(key, val) as usize;
198        }
199
200        replaced
201    }
202}
203
204const SLOT0_PRESENT: u8 = 1 << 0;
205const SLOT1_PRESENT: u8 = 1 << 1;
206const FG_SLOT: u8 = 1 << 2;
207
208/// Internal value container for a key in `OverlayMap`, holding up to two
209/// versions.
210///
211/// Each `Entry` stores a foreground value and optionally a background value.
212/// Used to track changes without cloning.
213#[derive(Debug)]
214struct Entry<T> {
215    bits: u8,
216    slots: [MaybeUninit<T>; 2],
217}
218
219impl<T> Entry<T> {
220    fn new(val: T) -> Self {
221        Self {
222            bits: SLOT0_PRESENT,
223            slots: [MaybeUninit::new(val), MaybeUninit::uninit()],
224        }
225    }
226
227    #[inline]
228    fn fg_index(&self) -> usize {
229        ((self.bits & FG_SLOT) >> 2) as usize
230    }
231
232    #[inline]
233    fn bg_index(&self) -> usize {
234        self.fg_index() ^ 1
235    }
236
237    fn fg(&self) -> &T {
238        let idx = self.fg_index();
239        unsafe { self.slots[idx].assume_init_ref() }
240    }
241
242    fn bg(&self) -> Option<&T> {
243        let idx = self.bg_index();
244        if self.is_slot_present(idx) {
245            Some(unsafe { self.slots[idx].assume_init_ref() })
246        } else {
247            None
248        }
249    }
250
251    #[inline]
252    fn is_slot_present(&self, idx: usize) -> bool {
253        (self.bits & (1 << idx)) != 0
254    }
255
256    /// Moves the current foreground value to the background slot, dropping any
257    /// previous background.
258    ///
259    /// This operation updates only internal bits and memory slots. The value
260    /// itself is not cloned or moved in memory. If a background value already
261    /// exists, it is dropped before being replaced.
262    #[inline]
263    fn push_fg_to_bg(&mut self) {
264        let bgi = self.bg_index();
265
266        // If the background slot is present, drop it to avoid memory leaks
267        if self.is_slot_present(bgi) {
268            unsafe {
269                self.slots[bgi].assume_init_drop();
270            }
271        }
272
273        // Flip the foreground/background logical mapping
274        self.bits |= 1 << bgi;
275        self.bits ^= FG_SLOT;
276    }
277
278    /// Insert a new value into the foreground, pushing the old one into the
279    /// background.
280    ///
281    /// This is called after `push_fg_to_bg` flips the active slot.
282    #[inline]
283    fn push(&mut self, val: T) {
284        self.push_fg_to_bg();
285        let idx = self.fg_index();
286        self.slots[idx] = MaybeUninit::new(val);
287        self.bits |= 1 << idx;
288    }
289}
290
291impl<V> Drop for Entry<V> {
292    fn drop(&mut self) {
293        if (self.bits & SLOT0_PRESENT) != 0 {
294            unsafe { self.slots[0].assume_init_drop() };
295        }
296
297        if (self.bits & SLOT1_PRESENT) != 0 {
298            unsafe { self.slots[1].assume_init_drop() };
299        }
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    #[test]
308    fn push_and_get_foreground() {
309        let mut map = OverlayMap::<&str, i32>::new();
310        assert!(map.fg(&"key").is_none());
311        map.push("key", 42);
312        assert_eq!(*map.fg(&"key").expect("Entry was just pushed"), 42);
313    }
314
315    #[test]
316    fn push_and_get_background() {
317        let mut map = OverlayMap::<&str, i32>::new();
318        assert!(map.fg(&"key").is_none());
319        map.push("key", 99);
320        assert_eq!(*map.fg(&"key").expect("Entry was just pushed"), 99);
321    }
322
323    #[test]
324    fn push_if_no_change_foreground() {
325        let mut map = OverlayMap::<&str, i32>::new();
326        map.push("key", 100);
327
328        // Try swap but do nothing
329        map.push_if(&"key", |old| {
330            if *old == 100 {
331                None // no change
332            } else {
333                Some(999)
334            }
335        });
336
337        // Still foreground = 100, no background
338        assert_eq!(*map.fg(&"key").expect("Entry should still exist"), 100);
339    }
340
341    #[test]
342    fn push_if_foreground_to_background() {
343        let mut map = OverlayMap::<&str, i32>::new();
344        map.push("key", 50);
345        map.push_if(&"key", |old| if *old == 50 { Some(123) } else { None });
346        assert_eq!(*map.fg(&"key").expect("Entry should still exist"), 123);
347        assert_eq!(
348            *map.bg(&"key").expect("Old value not found in background"),
349            50
350        );
351    }
352
353    #[test]
354    fn push_if_missing_key() {
355        let mut map = OverlayMap::<&str, i32>::new();
356        map.push_if(&"missing", |_| Some(999));
357        assert!(map.fg(&"missing").is_none());
358    }
359
360    #[test]
361    fn overlay_test() {
362        // Initialize an OverlayMap with:
363        // - "fg_key" in foreground
364        // - "bg_key" in background
365        // - "absent_key" absent
366        let mut map = OverlayMap::<&str, i32>::new();
367        map.push("fg_key", 10);
368        map.push("bg_key", 20);
369
370        // Prepare updates:
371        // - "fg_key" -> 100
372        // - "bg_key" -> 200
373        // - "none_key" -> 300 (was absent in map)
374        let updates = vec![("fg_key", 100), ("bg_key", 200), ("none_key", 300)];
375
376        // Perform the merge
377        map.overlay(updates);
378
379        // Check that "fg_key" was in foreground, so old value (10) moved to background.
380        // New value (100) should be in foreground.
381        match map.fg(&"fg_key") {
382            Some(val) => assert_eq!(*val, 100),
383            _ => panic!("Expected 'fg_key' = 100 in foreground"),
384        }
385        match map.bg(&"fg_key") {
386            Some(val) => assert_eq!(*val, 10),
387            None => panic!("Expected old 'fg_key' value = 10 in background"),
388        }
389
390        // Check that "none_key" was absent, so it is now in the foreground with 300
391        match map.fg(&"none_key") {
392            Some(val) => assert_eq!(*val, 300),
393            _ => panic!("Expected 'none_key' = 300 in foreground"),
394        }
395        // It shouldn't exist in background
396        assert!(map.bg(&"none_key").is_none());
397    }
398}