timed_map/
map.rs

1use super::*;
2
3use crate::iter::{GenericMapIntoIter, GenericMapIter, GenericMapIterMut, IntoIter, Iter, IterMut};
4
5macro_rules! cfg_std_feature {
6    ($($item:item)*) => {
7        $(
8            #[cfg(feature = "std")]
9            $item
10        )*
11    };
12}
13
14macro_rules! cfg_not_std_feature {
15    ($($item:item)*) => {
16        $(
17            #[cfg(not(feature = "std"))]
18            $item
19        )*
20    };
21}
22
23cfg_not_std_feature! {
24    /// Generic trait for `no_std` keys that is gated by the `std` feature
25    /// and handled at compile time.
26    pub trait GenericKey: Clone + Eq + Ord {}
27    impl<T: Clone + Eq + Ord> GenericKey for T {}
28}
29
30cfg_std_feature! {
31    /// Generic trait for `std` keys that is gated by the `std` feature
32    /// and handled at compile time.
33    pub trait GenericKey: Clone + Eq + Ord + Hash {}
34    impl<T: Clone + Eq + Ord + Hash> GenericKey for T {}
35}
36
37/// Wraps different map implementations and provides a single interface to access them.
38///
39/// TODO: Consider removing this type, instead, define a trait and update the internals
40/// of [`TimedMap`] to work with a generic value that implements this trait. This would
41/// allow users to implement the trait for their own custom map types, which means any
42/// map implementation can be supported from this library without needing to touch the
43/// library internals.
44#[allow(clippy::enum_variant_names)]
45#[derive(Clone, Debug)]
46enum GenericMap<K, V> {
47    BTreeMap(BTreeMap<K, V>),
48    #[cfg(feature = "std")]
49    HashMap(HashMap<K, V>),
50    #[cfg(all(feature = "std", feature = "rustc-hash"))]
51    FxHashMap(FxHashMap<K, V>),
52}
53
54impl<K, V> Default for GenericMap<K, V> {
55    fn default() -> Self {
56        Self::BTreeMap(BTreeMap::default())
57    }
58}
59
60impl<K, V> GenericMap<K, V>
61where
62    K: GenericKey,
63{
64    #[inline(always)]
65    fn get(&self, k: &K) -> Option<&V> {
66        match self {
67            Self::BTreeMap(inner) => inner.get(k),
68            #[cfg(feature = "std")]
69            Self::HashMap(inner) => inner.get(k),
70            #[cfg(all(feature = "std", feature = "rustc-hash"))]
71            Self::FxHashMap(inner) => inner.get(k),
72        }
73    }
74
75    #[inline(always)]
76    fn get_mut(&mut self, k: &K) -> Option<&mut V> {
77        match self {
78            Self::BTreeMap(inner) => inner.get_mut(k),
79            #[cfg(feature = "std")]
80            Self::HashMap(inner) => inner.get_mut(k),
81            #[cfg(all(feature = "std", feature = "rustc-hash"))]
82            Self::FxHashMap(inner) => inner.get_mut(k),
83        }
84    }
85
86    #[inline(always)]
87    fn len(&self) -> usize {
88        match self {
89            Self::BTreeMap(inner) => inner.len(),
90            #[cfg(feature = "std")]
91            Self::HashMap(inner) => inner.len(),
92            #[cfg(all(feature = "std", feature = "rustc-hash"))]
93            Self::FxHashMap(inner) => inner.len(),
94        }
95    }
96
97    #[inline(always)]
98    fn keys(&self) -> Vec<K> {
99        match self {
100            Self::BTreeMap(inner) => inner.keys().cloned().collect(),
101            #[cfg(feature = "std")]
102            Self::HashMap(inner) => inner.keys().cloned().collect(),
103            #[cfg(all(feature = "std", feature = "rustc-hash"))]
104            Self::FxHashMap(inner) => inner.keys().cloned().collect(),
105        }
106    }
107
108    #[inline(always)]
109    fn is_empty(&self) -> bool {
110        match self {
111            Self::BTreeMap(inner) => inner.is_empty(),
112            #[cfg(feature = "std")]
113            Self::HashMap(inner) => inner.is_empty(),
114            #[cfg(all(feature = "std", feature = "rustc-hash"))]
115            Self::FxHashMap(inner) => inner.is_empty(),
116        }
117    }
118
119    #[inline(always)]
120    fn insert(&mut self, k: K, v: V) -> Option<V> {
121        match self {
122            Self::BTreeMap(inner) => inner.insert(k, v),
123            #[cfg(feature = "std")]
124            Self::HashMap(inner) => inner.insert(k, v),
125            #[cfg(all(feature = "std", feature = "rustc-hash"))]
126            Self::FxHashMap(inner) => inner.insert(k, v),
127        }
128    }
129
130    #[inline(always)]
131    fn clear(&mut self) {
132        match self {
133            Self::BTreeMap(inner) => inner.clear(),
134            #[cfg(feature = "std")]
135            Self::HashMap(inner) => inner.clear(),
136            #[cfg(all(feature = "std", feature = "rustc-hash"))]
137            Self::FxHashMap(inner) => inner.clear(),
138        }
139    }
140
141    #[inline(always)]
142    fn remove(&mut self, k: &K) -> Option<V> {
143        match self {
144            Self::BTreeMap(inner) => inner.remove(k),
145            #[cfg(feature = "std")]
146            Self::HashMap(inner) => inner.remove(k),
147            #[cfg(all(feature = "std", feature = "rustc-hash"))]
148            Self::FxHashMap(inner) => inner.remove(k),
149        }
150    }
151
152    fn iter(&self) -> GenericMapIter<'_, K, V> {
153        match self {
154            Self::BTreeMap(inner) => GenericMapIter::BTreeMap(inner.iter()),
155            #[cfg(feature = "std")]
156            Self::HashMap(inner) => GenericMapIter::HashMap(inner.iter()),
157            #[cfg(all(feature = "std", feature = "rustc-hash"))]
158            Self::FxHashMap(inner) => GenericMapIter::FxHashMap(inner.iter()),
159        }
160    }
161
162    fn into_iter(self) -> GenericMapIntoIter<K, V> {
163        match self {
164            Self::BTreeMap(inner) => GenericMapIntoIter::BTreeMap(inner.into_iter()),
165            #[cfg(feature = "std")]
166            Self::HashMap(inner) => GenericMapIntoIter::HashMap(inner.into_iter()),
167            #[cfg(all(feature = "std", feature = "rustc-hash"))]
168            Self::FxHashMap(inner) => GenericMapIntoIter::FxHashMap(inner.into_iter()),
169        }
170    }
171
172    fn iter_mut(&mut self) -> GenericMapIterMut<'_, K, V> {
173        match self {
174            Self::BTreeMap(inner) => GenericMapIterMut::BTreeMap(inner.iter_mut()),
175            #[cfg(feature = "std")]
176            Self::HashMap(inner) => GenericMapIterMut::HashMap(inner.iter_mut()),
177            #[cfg(all(feature = "std", feature = "rustc-hash"))]
178            Self::FxHashMap(inner) => GenericMapIterMut::FxHashMap(inner.iter_mut()),
179        }
180    }
181}
182
183/// Specifies the inner map implementation for `TimedMap`.
184#[cfg(feature = "std")]
185#[allow(clippy::enum_variant_names)]
186pub enum MapKind {
187    BTreeMap,
188    HashMap,
189    #[cfg(feature = "rustc-hash")]
190    FxHashMap,
191}
192
193/// Associates keys of type `K` with values of type `V`. Each entry may optionally expire after a
194/// specified duration.
195///
196/// Mutable functions automatically clears expired entries when called.
197///
198/// If no expiration is set, the entry remains constant.
199#[derive(Clone, Debug)]
200pub struct TimedMap<K, V, #[cfg(feature = "std")] C = StdClock, #[cfg(not(feature = "std"))] C> {
201    clock: C,
202
203    map: GenericMap<K, ExpirableEntry<V>>,
204    expiries: BTreeMap<u64, BTreeSet<K>>,
205
206    expiration_tick: u16,
207    expiration_tick_cap: u16,
208}
209
210#[cfg(feature = "serde")]
211impl<K: serde::Serialize + Ord, V: serde::Serialize, C: Clock> serde::Serialize
212    for TimedMap<K, V, C>
213{
214    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
215    where
216        S: serde::Serializer,
217    {
218        let now = self.clock.elapsed_seconds_since_creation();
219        match &self.map {
220            GenericMap::BTreeMap(inner) => {
221                let map = inner.iter().filter(|(_k, v)| !v.is_expired(now));
222                serializer.collect_map(map)
223            }
224            #[cfg(feature = "std")]
225            GenericMap::HashMap(inner) => {
226                let map = inner.iter().filter(|(_k, v)| !v.is_expired(now));
227                serializer.collect_map(map)
228            }
229            #[cfg(all(feature = "std", feature = "rustc-hash"))]
230            GenericMap::FxHashMap(inner) => {
231                let map = inner.iter().filter(|(_k, v)| !v.is_expired(now));
232                serializer.collect_map(map)
233            }
234        }
235    }
236}
237
238impl<'a, K, V, C> IntoIterator for &'a TimedMap<K, V, C>
239where
240    K: GenericKey,
241    C: Clock,
242{
243    type Item = (&'a K, &'a V);
244    type IntoIter = Iter<'a, K, V>;
245
246    fn into_iter(self) -> Self::IntoIter {
247        let now = self.clock.elapsed_seconds_since_creation();
248        Iter {
249            inner: self.map.iter(),
250            now,
251        }
252    }
253}
254
255impl<'a, K, V, C> IntoIterator for &'a mut TimedMap<K, V, C>
256where
257    K: GenericKey,
258    C: Clock,
259{
260    type Item = (&'a K, &'a mut V);
261    type IntoIter = IterMut<'a, K, V>;
262
263    fn into_iter(self) -> Self::IntoIter {
264        let now = self.clock.elapsed_seconds_since_creation();
265        IterMut {
266            inner: self.map.iter_mut(),
267            now,
268        }
269    }
270}
271
272impl<K, V, C> IntoIterator for TimedMap<K, V, C>
273where
274    K: GenericKey,
275    C: Clock,
276{
277    type Item = (K, V);
278    type IntoIter = IntoIter<K, V>;
279
280    fn into_iter(self) -> Self::IntoIter {
281        let now = self.clock.elapsed_seconds_since_creation();
282        IntoIter {
283            inner: self.map.into_iter(),
284            now,
285        }
286    }
287}
288
289impl<K, V, C> Default for TimedMap<K, V, C>
290where
291    C: Default,
292{
293    fn default() -> Self {
294        Self {
295            clock: Default::default(),
296
297            map: GenericMap::default(),
298            expiries: BTreeMap::default(),
299
300            expiration_tick: 0,
301            expiration_tick_cap: 1,
302        }
303    }
304}
305
306#[cfg(feature = "std")]
307impl<K, V> TimedMap<K, V, StdClock>
308where
309    K: GenericKey,
310{
311    /// Creates an empty map.
312    pub fn new() -> Self {
313        Self::default()
314    }
315
316    /// Creates an empty map based on the chosen map implementation specified by `MapKind`.
317    pub fn new_with_map_kind(map_kind: MapKind) -> Self {
318        let map = match map_kind {
319            MapKind::BTreeMap => GenericMap::<K, ExpirableEntry<V>>::BTreeMap(BTreeMap::default()),
320            MapKind::HashMap => GenericMap::HashMap(HashMap::default()),
321            #[cfg(feature = "rustc-hash")]
322            MapKind::FxHashMap => GenericMap::FxHashMap(FxHashMap::default()),
323        };
324
325        Self {
326            map,
327
328            clock: StdClock::default(),
329            expiries: BTreeMap::default(),
330
331            expiration_tick: 0,
332            expiration_tick_cap: 1,
333        }
334    }
335}
336
337impl<K, V, C> TimedMap<K, V, C>
338where
339    C: Clock,
340    K: GenericKey,
341{
342    /// Creates an empty `TimedMap`.
343    ///
344    /// Uses the provided `clock` to handle expiration times.
345    #[cfg(not(feature = "std"))]
346    pub fn new(clock: C) -> Self {
347        Self {
348            clock,
349            map: GenericMap::default(),
350            expiries: BTreeMap::default(),
351            expiration_tick: 0,
352            expiration_tick_cap: 1,
353        }
354    }
355
356    /// Configures `expiration_tick_cap`, which sets how often `TimedMap::drop_expired_entries`
357    /// is automatically called. The default value is 1.
358    ///
359    /// On each insert (excluding `unchecked` ones), an internal counter `expiration_tick` is incremented.
360    /// When `expiration_tick` meets or exceeds `expiration_tick_cap`, `TimedMap::drop_expired_entries` is
361    /// triggered to remove expired entries.
362    ///
363    /// Use this to control cleanup frequency and optimize performance. For example, if your workload
364    /// involves about 100 inserts within couple seconds, setting `expiration_tick_cap` to 100 can improve
365    /// the performance significantly.
366    #[inline(always)]
367    pub fn expiration_tick_cap(mut self, expiration_tick_cap: u16) -> Self {
368        self.expiration_tick_cap = expiration_tick_cap;
369        self
370    }
371
372    /// Returns the associated value if present and not expired.
373    ///
374    /// To retrieve the value without checking expiration, use `TimedMap::get_unchecked`.
375    pub fn get(&self, k: &K) -> Option<&V> {
376        self.map
377            .get(k)
378            .filter(|v| !v.is_expired(self.clock.elapsed_seconds_since_creation()))
379            .map(|v| v.value())
380    }
381
382    /// Returns a mutable reference to the value corresponding to the key.
383    ///
384    /// To retrieve the value without checking expiration, use `TimedMap::get_mut_unchecked`.
385    pub fn get_mut(&mut self, k: &K) -> Option<&mut V> {
386        self.map
387            .get_mut(k)
388            .filter(|v| !v.is_expired(self.clock.elapsed_seconds_since_creation()))
389            .map(|v| v.value_mut())
390    }
391
392    /// Returns the associated value if present, regardless of whether it is expired.
393    ///
394    /// If you only want non-expired entries, use `TimedMap::get` instead.
395    #[inline(always)]
396    pub fn get_unchecked(&self, k: &K) -> Option<&V> {
397        self.map.get(k).map(|v| v.value())
398    }
399
400    /// Returns a mutable reference to the associated value if present, regardless of
401    /// whether it is expired.
402    ///
403    /// If you only want non-expired entries, use `TimedMap::get_mut` instead.
404    #[inline(always)]
405    pub fn get_mut_unchecked(&mut self, k: &K) -> Option<&mut V> {
406        self.map.get_mut(k).map(|v| v.value_mut())
407    }
408
409    /// Returns the associated value's `Duration` if present and not expired.
410    ///
411    /// Returns `None` if the entry does not exist or is constant.
412    pub fn get_remaining_duration(&self, k: &K) -> Option<Duration> {
413        match self.map.get(k) {
414            Some(v) => {
415                let now = self.clock.elapsed_seconds_since_creation();
416                if v.is_expired(now) {
417                    return None;
418                }
419
420                v.remaining_duration(now)
421            }
422            None => None,
423        }
424    }
425
426    /// Returns the number of unexpired elements in the map.
427    ///
428    /// See `TimedMap::len_expired` and `TimedMap::len_unchecked` for other usages.
429    #[inline(always)]
430    pub fn len(&self) -> usize {
431        self.map.len() - self.len_expired()
432    }
433
434    /// Returns the number of expired elements in the map.
435    ///
436    /// See `TimedMap::len` and `TimedMap::len_unchecked` for other usages.
437    #[inline(always)]
438    pub fn len_expired(&self) -> usize {
439        let now = self.clock.elapsed_seconds_since_creation();
440        self.expiries
441            .range(..=now)
442            .map(|(_exp, keys)| keys.len())
443            .sum()
444    }
445
446    /// Returns the total number of elements (including expired ones) in the map.
447    ///
448    /// See `TimedMap::len` and `TimedMap::len_expired` for other usages.
449    #[inline(always)]
450    pub fn len_unchecked(&self) -> usize {
451        self.map.len()
452    }
453
454    /// Returns keys of the map
455    #[inline(always)]
456    pub fn keys(&self) -> Vec<K> {
457        self.map.keys()
458    }
459
460    /// Returns true if the map contains no elements.
461    #[inline(always)]
462    pub fn is_empty(&self) -> bool {
463        self.map.is_empty()
464    }
465
466    /// Inserts a key-value pair with an expiration duration. If duration is `None`,
467    /// entry will be stored in a non-expirable way.
468    ///
469    /// If a value already exists for the given key, it will be updated and then
470    /// the old one will be returned.
471    #[inline(always)]
472    fn insert(&mut self, k: K, v: V, expires_at: Option<u64>) -> Option<V> {
473        let entry = ExpirableEntry::new(v, expires_at);
474        match self.map.insert(k.clone(), entry) {
475            Some(old) => {
476                // Remove the old expiry record
477                if let EntryStatus::ExpiresAtSeconds(e) = old.status() {
478                    self.drop_key_from_expiry(e, &k)
479                }
480
481                Some(old.owned_value())
482            }
483            None => None,
484        }
485    }
486
487    /// Inserts a key-value pair with an expiration duration, and then drops the
488    /// expired entries.
489    ///
490    /// If a value already exists for the given key, it will be updated and then
491    /// the old one will be returned.
492    ///
493    /// If you don't want to the check expired entries, consider using `TimedMap::insert_expirable_unchecked`
494    /// instead.
495    pub fn insert_expirable(&mut self, k: K, v: V, duration: Duration) -> Option<V> {
496        self.expiration_tick += 1;
497
498        let now = self.clock.elapsed_seconds_since_creation();
499        if self.expiration_tick >= self.expiration_tick_cap {
500            self.drop_expired_entries_inner(now);
501            self.expiration_tick = 0;
502        }
503
504        let expires_at = now + duration.as_secs();
505
506        let res = self.insert(k.clone(), v, Some(expires_at));
507
508        self.expiries.entry(expires_at).or_default().insert(k);
509
510        res
511    }
512
513    /// Inserts a key-value pair with an expiration duration, without checking the expired
514    /// entries.
515    ///
516    /// If a value already exists for the given key, it will be updated and then
517    /// the old one will be returned.
518    ///
519    /// If you want to check the expired entries, consider using `TimedMap::insert_expirable`
520    /// instead.
521    pub fn insert_expirable_unchecked(&mut self, k: K, v: V, duration: Duration) -> Option<V> {
522        let now = self.clock.elapsed_seconds_since_creation();
523        let expires_at = now + duration.as_secs();
524
525        let res = self.insert(k.clone(), v, Some(expires_at));
526
527        self.expiries.entry(expires_at).or_default().insert(k);
528
529        res
530    }
531
532    /// Inserts a key-value pair with that doesn't expire, and then drops the
533    /// expired entries.
534    ///
535    /// If a value already exists for the given key, it will be updated and then
536    /// the old one will be returned.
537    ///
538    /// If you don't want to check the expired entries, consider using `TimedMap::insert_constant_unchecked`
539    /// instead.
540    pub fn insert_constant(&mut self, k: K, v: V) -> Option<V> {
541        self.expiration_tick += 1;
542
543        let now = self.clock.elapsed_seconds_since_creation();
544        if self.expiration_tick >= self.expiration_tick_cap {
545            self.drop_expired_entries_inner(now);
546            self.expiration_tick = 0;
547        }
548
549        self.insert(k, v, None)
550    }
551
552    /// Inserts a key-value pair with that doesn't expire without checking the expired
553    /// entries.
554    ///
555    /// If a value already exists for the given key, it will be updated and then
556    /// the old one will be returned.
557    ///
558    /// If you want to check the expired entries, consider using `TimedMap::insert_constant`
559    /// instead.
560    pub fn insert_constant_unchecked(&mut self, k: K, v: V) -> Option<V> {
561        self.expiration_tick += 1;
562        self.insert(k, v, None)
563    }
564
565    /// Removes a key-value pair from the map and returns the associated value if present
566    /// and not expired.
567    ///
568    /// If you want to retrieve the entry after removal even if it is expired, consider using
569    /// `TimedMap::remove_unchecked`.
570    #[inline(always)]
571    pub fn remove(&mut self, k: &K) -> Option<V> {
572        self.map
573            .remove(k)
574            .filter(|v| {
575                if let EntryStatus::ExpiresAtSeconds(expires_at_seconds) = v.status() {
576                    self.drop_key_from_expiry(expires_at_seconds, k);
577                }
578
579                !v.is_expired(self.clock.elapsed_seconds_since_creation())
580            })
581            .map(|v| v.owned_value())
582    }
583
584    /// Removes a key-value pair from the map and returns the associated value if present,
585    /// regardless of expiration status.
586    ///
587    /// If you only want the entry when it is not expired, consider using `TimedMap::remove`.
588    #[inline(always)]
589    pub fn remove_unchecked(&mut self, k: &K) -> Option<V> {
590        self.map
591            .remove(k)
592            .filter(|v| {
593                if let EntryStatus::ExpiresAtSeconds(expires_at_seconds) = v.status() {
594                    self.drop_key_from_expiry(expires_at_seconds, k);
595                }
596
597                true
598            })
599            .map(|v| v.owned_value())
600    }
601
602    /// Clears the map, removing all elements.
603    #[inline(always)]
604    pub fn clear(&mut self) {
605        self.map.clear()
606    }
607
608    /// Returns an iterator over non-expired key-value pairs.
609    pub fn iter(&self) -> Iter<'_, K, V> {
610        self.into_iter()
611    }
612
613    /// Returns an iterator over all key-value pairs, including expired ones.
614    pub fn iter_unchecked(&self) -> impl Iterator<Item = (&K, &V)> {
615        self.map.iter().map(|(k, v)| (k, v.value()))
616    }
617
618    /// Returns a mutable iterator over non-expired key-value pairs.
619    pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
620        self.into_iter()
621    }
622
623    /// Returns a mutable iterator over all key-value pairs, including expired ones.
624    pub fn iter_mut_unchecked(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
625        self.map.iter_mut().map(|(k, v)| (k, v.value_mut()))
626    }
627
628    /// Updates the expiration status of an entry and returns the old status.
629    ///
630    /// If the entry does not exist, returns Err.
631    /// If the entry's old status is `EntryStatus::Constant`, returns None.
632    pub fn update_expiration_status(
633        &mut self,
634        key: K,
635        duration: Duration,
636    ) -> Result<Option<EntryStatus>, &'static str> {
637        match self.map.get_mut(&key) {
638            Some(entry) => {
639                let old_status = *entry.status();
640                let now = self.clock.elapsed_seconds_since_creation();
641                let expires_at = now + duration.as_secs();
642
643                entry.update_status(EntryStatus::ExpiresAtSeconds(expires_at));
644
645                if let EntryStatus::ExpiresAtSeconds(t) = &old_status {
646                    self.drop_key_from_expiry(t, &key);
647                }
648                self.expiries
649                    .entry(expires_at)
650                    .or_default()
651                    .insert(key.clone());
652
653                Ok(Some(old_status))
654            }
655            None => Err("entry not found"),
656        }
657    }
658
659    /// Clears expired entries from the map and returns them.
660    ///
661    /// Call this function when using `*_unchecked` inserts, as these do not
662    /// automatically clear expired entries.
663    #[inline(always)]
664    pub fn drop_expired_entries(&mut self) -> Vec<(K, V)> {
665        let now = self.clock.elapsed_seconds_since_creation();
666        self.drop_expired_entries_inner(now)
667    }
668
669    fn drop_expired_entries_inner(&mut self, now: u64) -> Vec<(K, V)> {
670        let mut expired_entries = Vec::new();
671        // Iterates through `expiries` in order and drops expired ones.
672        while let Some((exp, keys)) = self.expiries.pop_first() {
673            // It's safe to do early-break here as keys are sorted by expiration.
674            if exp > now {
675                self.expiries.insert(exp, keys);
676                break;
677            }
678
679            for key in keys {
680                if let Some(value) = self.map.remove(&key) {
681                    expired_entries.push((key, value.owned_value()));
682                }
683            }
684        }
685
686        expired_entries
687    }
688
689    fn drop_key_from_expiry(&mut self, expiry_key: &u64, map_key: &K) {
690        if let Some(list) = self.expiries.get_mut(expiry_key) {
691            list.remove(map_key);
692
693            if list.is_empty() {
694                self.expiries.remove(expiry_key);
695            }
696        }
697    }
698
699    /// Returns `true` if the map contains a non-expired value for the given key.
700    ///
701    /// To include expired entries as well, use [`TimedMap::contains_key_unchecked`].
702    #[inline(always)]
703    pub fn contains_key(&self, k: &K) -> bool {
704        self.get(k).is_some()
705    }
706
707    /// Returns `true` if the map contains a value for the given key regardless of expiration status.
708    ///
709    /// To exclude expired entries, use [`TimedMap::contains_key`] instead.
710    #[inline(always)]
711    pub fn contains_key_unchecked(&self, k: &K) -> bool {
712        self.get_unchecked(k).is_some()
713    }
714}
715
716#[cfg(test)]
717#[cfg(not(feature = "std"))]
718mod tests {
719    use super::*;
720
721    #[derive(Clone, Copy)]
722    struct MockClock {
723        current_time: u64,
724    }
725
726    impl Clock for MockClock {
727        fn elapsed_seconds_since_creation(&self) -> u64 {
728            self.current_time
729        }
730    }
731
732    #[test]
733    fn nostd_insert_and_get_constant_entry() {
734        let clock = MockClock { current_time: 1000 };
735        let mut map = TimedMap::new(clock);
736
737        map.insert_constant(1, "constant value");
738
739        assert_eq!(map.get(&1), Some(&"constant value"));
740        assert_eq!(map.get_remaining_duration(&1), None);
741    }
742
743    #[test]
744    fn nostd_insert_and_get_expirable_entry() {
745        let clock = MockClock { current_time: 1000 };
746        let mut map = TimedMap::new(clock);
747        let duration = Duration::from_secs(60);
748
749        map.insert_expirable(1, "expirable value", duration);
750
751        assert_eq!(map.get(&1), Some(&"expirable value"));
752        assert_eq!(map.get_remaining_duration(&1), Some(duration));
753    }
754
755    #[test]
756    fn nostd_expired_entry() {
757        let clock = MockClock { current_time: 1000 };
758        let mut map = TimedMap::new(clock);
759        let duration = Duration::from_secs(60);
760
761        // Insert entry that expires in 60 seconds
762        map.insert_expirable(1, "expirable value", duration);
763
764        // Simulate time passage beyond expiration
765        let clock = MockClock { current_time: 1070 };
766        map.clock = clock;
767
768        // The entry should be considered expired
769        assert_eq!(map.get(&1), None);
770        assert_eq!(map.get_remaining_duration(&1), None);
771    }
772
773    #[test]
774    fn nostd_remove_entry() {
775        let clock = MockClock { current_time: 1000 };
776        let mut map = TimedMap::new(clock);
777
778        map.insert_constant(1, "constant value");
779
780        assert_eq!(map.remove(&1), Some("constant value"));
781        assert_eq!(map.get(&1), None);
782    }
783
784    #[test]
785    fn nostd_drop_expired_entries() {
786        let clock = MockClock { current_time: 1000 };
787        let mut map = TimedMap::new(clock);
788
789        // Insert one constant and 2 expirable entries
790        map.insert_expirable(1, "expirable value1", Duration::from_secs(50));
791        map.insert_expirable(2, "expirable value2", Duration::from_secs(70));
792        map.insert_constant(3, "constant value");
793
794        // Simulate time passage beyond the expiration of the first entry
795        let clock = MockClock { current_time: 1055 };
796        map.clock = clock;
797
798        // Entry 1 should be removed and entry 2 and 3 should still exist
799        assert_eq!(map.get(&1), None);
800        assert_eq!(map.get(&2), Some(&"expirable value2"));
801        assert_eq!(map.get(&3), Some(&"constant value"));
802
803        // Simulate time passage again to expire second expirable entry
804        let clock = MockClock { current_time: 1071 };
805        map.clock = clock;
806
807        assert_eq!(map.get(&1), None);
808        assert_eq!(map.get(&2), None);
809        assert_eq!(map.get(&3), Some(&"constant value"));
810    }
811
812    #[test]
813    fn nostd_update_existing_entry() {
814        let clock = MockClock { current_time: 1000 };
815        let mut map = TimedMap::new(clock);
816
817        map.insert_constant(1, "initial value");
818        assert_eq!(map.get(&1), Some(&"initial value"));
819
820        // Update the value of the existing key and make it expirable
821        map.insert_expirable(1, "updated value", Duration::from_secs(15));
822        assert_eq!(map.get(&1), Some(&"updated value"));
823
824        // Simulate time passage and expire the updated entry
825        let clock = MockClock { current_time: 1016 };
826        map.clock = clock;
827
828        assert_eq!(map.get(&1), None);
829    }
830
831    #[test]
832    fn nostd_update_expirable_entry_status() {
833        let clock = MockClock { current_time: 1000 };
834        let mut map = TimedMap::new(clock);
835
836        map.insert_constant(1, "initial value");
837        assert_eq!(map.get(&1), Some(&"initial value"));
838
839        // Update the value of the existing key and make it expirable
840        map.update_expiration_status(1, Duration::from_secs(16))
841            .expect("entry update shouldn't fail");
842        assert_eq!(map.get(&1), Some(&"initial value"));
843
844        // Simulate time passage and expire the updated entry
845        let clock = MockClock { current_time: 1017 };
846        map.clock = clock;
847        assert_eq!(map.get(&1), None);
848    }
849
850    #[test]
851    fn nostd_update_expirable_entry_status_with_previou_time() {
852        let clock = MockClock { current_time: 1000 };
853        let mut map = TimedMap::new(clock);
854
855        // Insert map entry followed by immediately updating expiration time
856        map.insert_expirable(1, "expirable value", Duration::from_secs(15));
857        map.update_expiration_status(1, Duration::from_secs(15))
858            .expect("entry update shouldn't fail");
859
860        // We should still have our entry.
861        assert_eq!(map.get(&1), Some(&"expirable value"));
862        assert!(map.expiries.contains_key(&1015));
863    }
864
865    #[test]
866    fn nostd_contains_key() {
867        let clock = MockClock { current_time: 1000 };
868        let mut map = TimedMap::new(clock);
869
870        // Insert map entry and check if exists
871        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
872        assert!(map.contains_key(&1));
873    }
874
875    #[test]
876    fn nostd_iter_empty() {
877        let clock = MockClock { current_time: 1000 };
878        let map: TimedMap<u64, &str, MockClock> = TimedMap::new(clock);
879        assert_eq!(map.iter().count(), 0);
880        assert_eq!(map.into_iter().count(), 0);
881    }
882
883    #[test]
884    fn nostd_iter_all_expired() {
885        let mut clock = MockClock { current_time: 1000 };
886        let mut map = TimedMap::new(clock);
887
888        // Expires at 1001
889        map.insert_expirable(1, "val1", Duration::from_secs(1));
890        // Expires at 1002
891        map.insert_expirable(2, "val2", Duration::from_secs(2));
892
893        clock.current_time = 1003;
894        map.clock = clock;
895
896        assert_eq!(map.iter().count(), 0);
897        assert_eq!(map.into_iter().count(), 0);
898    }
899
900    #[test]
901    fn nostd_iter_mixed() {
902        let mut clock = MockClock { current_time: 1000 };
903        let mut map = TimedMap::new(clock);
904
905        // Expires at 1001
906        map.insert_expirable(1, "val1", Duration::from_secs(1));
907        // Never expires
908        map.insert_constant(2, "val2");
909        // Expires at 1005
910        map.insert_expirable(3, "val3", Duration::from_secs(5));
911
912        clock.current_time = 1002;
913        map.clock = clock;
914
915        let mut collected: Vec<(&u64, &&str)> = map.iter().collect();
916        collected.sort_by_key(|(k, _)| *k);
917
918        assert_eq!(collected.len(), 2);
919        assert_eq!(collected[0], (&2, &"val2"));
920        assert_eq!(collected[1], (&3, &"val3"));
921    }
922
923    #[test]
924    fn nostd_iter_mut_mixed() {
925        let mut clock = MockClock { current_time: 1000 };
926        let mut map = TimedMap::new(clock);
927
928        // Expires at 1001
929        map.insert_expirable(1, "val1", Duration::from_secs(1));
930        // Never expires
931        map.insert_constant(2, "val2");
932        // Expires at 1005
933        map.insert_expirable(3, "val3", Duration::from_secs(5));
934
935        clock.current_time = 1002;
936        map.clock = clock;
937
938        for (k, v) in map.iter_mut() {
939            if *k == 2 {
940                *v = "modified_val2";
941            }
942        }
943
944        assert_eq!(map.get(&1), None);
945        assert_eq!(map.get(&2), Some(&"modified_val2"));
946        assert_eq!(map.get(&3), Some(&"val3"));
947    }
948}
949
950#[cfg(feature = "std")]
951#[cfg(test)]
952mod std_tests {
953    use core::ops::Add;
954
955    use super::*;
956
957    #[test]
958    fn std_expirable_and_constant_entries() {
959        let mut map = TimedMap::new();
960
961        map.insert_constant(1, "constant value");
962        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
963
964        assert_eq!(map.get(&1), Some(&"constant value"));
965        assert_eq!(map.get(&2), Some(&"expirable value"));
966
967        assert_eq!(map.get_remaining_duration(&1), None);
968        assert!(map.get_remaining_duration(&2).is_some());
969    }
970
971    #[test]
972    fn std_expired_entry_removal() {
973        let mut map = TimedMap::new();
974        let duration = Duration::from_secs(2);
975
976        map.insert_expirable(1, "expirable value", duration);
977
978        // Wait for expiration
979        std::thread::sleep(Duration::from_secs(3));
980
981        // Entry should now be expired
982        assert_eq!(map.get(&1), None);
983        assert_eq!(map.get_remaining_duration(&1), None);
984    }
985
986    #[test]
987    fn std_remove_entry() {
988        let mut map = TimedMap::new();
989
990        map.insert_constant(1, "constant value");
991        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
992
993        assert_eq!(map.remove(&1), Some("constant value"));
994        assert_eq!(map.remove(&2), Some("expirable value"));
995
996        assert_eq!(map.get(&1), None);
997        assert_eq!(map.get(&2), None);
998    }
999
1000    #[test]
1001    fn std_drop_expired_entries() {
1002        let mut map = TimedMap::new();
1003
1004        map.insert_expirable(1, "expirable value1", Duration::from_secs(2));
1005        map.insert_expirable(2, "expirable value2", Duration::from_secs(4));
1006
1007        // Wait for expiration
1008        std::thread::sleep(Duration::from_secs(3));
1009
1010        // Entry 1 should be removed and entry 2 should still exist
1011        assert_eq!(map.get(&1), None);
1012        assert_eq!(map.get(&2), Some(&"expirable value2"));
1013    }
1014
1015    #[test]
1016    fn std_update_existing_entry() {
1017        let mut map = TimedMap::new();
1018
1019        map.insert_constant(1, "initial value");
1020        assert_eq!(map.get(&1), Some(&"initial value"));
1021
1022        // Update the value of the existing key and make it expirable
1023        map.insert_expirable(1, "updated value", Duration::from_secs(1));
1024        assert_eq!(map.get(&1), Some(&"updated value"));
1025
1026        std::thread::sleep(Duration::from_secs(2));
1027
1028        // Should be expired now
1029        assert_eq!(map.get(&1), None);
1030    }
1031
1032    #[test]
1033    fn std_insert_constant_and_expirable_combined() {
1034        let mut map = TimedMap::new();
1035
1036        // Insert a constant entry and an expirable entry
1037        map.insert_constant(1, "constant value");
1038        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
1039
1040        // Check both entries exist
1041        assert_eq!(map.get(&1), Some(&"constant value"));
1042        assert_eq!(map.get(&2), Some(&"expirable value"));
1043
1044        // Simulate passage of time beyond expiration
1045        std::thread::sleep(Duration::from_secs(3));
1046
1047        // Constant entry should still exist, expirable should be expired
1048        assert_eq!(map.get(&1), Some(&"constant value"));
1049        assert_eq!(map.get(&2), None);
1050    }
1051
1052    #[test]
1053    fn std_expirable_entry_still_valid_before_expiration() {
1054        let mut map = TimedMap::new();
1055
1056        // Insert an expirable entry with a duration of 3 seconds
1057        map.insert_expirable(1, "expirable value", Duration::from_secs(3));
1058
1059        // Simulate a short sleep of 2 seconds (still valid)
1060        std::thread::sleep(Duration::from_secs(2));
1061
1062        // The entry should still be valid
1063        assert_eq!(map.get(&1), Some(&"expirable value"));
1064        assert!(map.get_remaining_duration(&1).unwrap().as_secs() == 1);
1065    }
1066
1067    #[test]
1068    fn std_length_functions() {
1069        let mut map = TimedMap::new();
1070
1071        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1072        map.insert_expirable(2, "expirable value", Duration::from_secs(1));
1073        map.insert_expirable(3, "expirable value", Duration::from_secs(3));
1074        map.insert_expirable(4, "expirable value", Duration::from_secs(3));
1075        map.insert_expirable(5, "expirable value", Duration::from_secs(3));
1076        map.insert_expirable(6, "expirable value", Duration::from_secs(3));
1077
1078        std::thread::sleep(Duration::from_secs(2).add(Duration::from_millis(1)));
1079
1080        assert_eq!(map.len(), 4);
1081        assert_eq!(map.len_expired(), 2);
1082        assert_eq!(map.len_unchecked(), 6);
1083    }
1084
1085    #[test]
1086    fn std_update_expirable_entry() {
1087        let mut map = TimedMap::new();
1088
1089        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1090        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
1091
1092        std::thread::sleep(Duration::from_secs(2));
1093
1094        assert!(!map.expiries.contains_key(&1));
1095        assert!(map.expiries.contains_key(&5));
1096        assert_eq!(map.get(&1), Some(&"expirable value"));
1097    }
1098
1099    #[test]
1100    fn std_update_expirable_entry_status() {
1101        let mut map = TimedMap::new();
1102
1103        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1104        map.update_expiration_status(1, Duration::from_secs(5))
1105            .expect("entry update shouldn't fail");
1106
1107        std::thread::sleep(Duration::from_secs(3));
1108        assert!(!map.expiries.contains_key(&1));
1109        assert!(map.expiries.contains_key(&5));
1110        assert_eq!(map.get(&1), Some(&"expirable value"));
1111    }
1112
1113    #[test]
1114    fn std_update_expirable_entry_status_with_previou_time() {
1115        let mut map = TimedMap::new();
1116
1117        // Insert map entry followed by immediately updating expiration time
1118        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
1119        map.update_expiration_status(1, Duration::from_secs(5))
1120            .expect("entry update shouldn't fail");
1121
1122        // We should still have our entry.
1123        assert_eq!(map.get(&1), Some(&"expirable value"));
1124        assert!(map.expiries.contains_key(&5));
1125    }
1126
1127    #[test]
1128    fn std_contains_key() {
1129        let mut map = TimedMap::new();
1130
1131        // Insert map entry and check if exists
1132        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1133        assert!(map.contains_key(&1));
1134    }
1135
1136    #[test]
1137    fn std_does_not_contain_key_anymore() {
1138        let mut map = TimedMap::new();
1139
1140        // Insert map entry and check if still exists after expiry
1141        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1142        std::thread::sleep(Duration::from_secs(2));
1143        assert!(!map.contains_key(&1));
1144    }
1145
1146    #[test]
1147    fn std_contains_key_unchecked() {
1148        let mut map = TimedMap::new();
1149
1150        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1151        std::thread::sleep(Duration::from_secs(2));
1152        assert!(map.contains_key_unchecked(&1));
1153    }
1154
1155    #[test]
1156    fn std_iter_empty() {
1157        let map: TimedMap<u64, &str> = TimedMap::new();
1158        assert_eq!(map.iter().count(), 0);
1159        assert_eq!(map.into_iter().count(), 0);
1160    }
1161
1162    #[test]
1163    fn std_iter_all_expired() {
1164        let mut map = TimedMap::new();
1165
1166        map.insert_expirable(1, "val1", Duration::from_secs(1));
1167        map.insert_expirable(2, "val2", Duration::from_secs(1));
1168
1169        std::thread::sleep(Duration::from_secs(2));
1170
1171        assert_eq!(map.iter().count(), 0);
1172        assert_eq!(map.into_iter().count(), 0);
1173    }
1174
1175    #[test]
1176    fn std_iter_mixed() {
1177        let mut map = TimedMap::new();
1178
1179        map.insert_expirable(1, "val1", Duration::from_secs(1));
1180        map.insert_constant(2, "val2");
1181        map.insert_expirable(3, "val3", Duration::from_secs(5));
1182
1183        // Only 1 should expire after this sleep
1184        std::thread::sleep(Duration::from_secs(2));
1185
1186        let mut collected: Vec<(&u64, &&str)> = map.iter().collect();
1187        collected.sort_by_key(|(k, _)| *k);
1188
1189        assert_eq!(collected.len(), 2);
1190        assert_eq!(collected[0], (&2, &"val2"));
1191        assert_eq!(collected[1], (&3, &"val3"));
1192    }
1193
1194    #[test]
1195    fn std_iter_mut_mixed() {
1196        let mut map = TimedMap::new();
1197
1198        map.insert_expirable(1, "val1", Duration::from_secs(1));
1199        map.insert_constant(2, "val2");
1200        map.insert_expirable(3, "val3", Duration::from_secs(5));
1201
1202        // Only 1 should expire after this sleep
1203        std::thread::sleep(Duration::from_secs(2));
1204
1205        for (k, v) in map.iter_mut() {
1206            if *k == 2 {
1207                *v = "modified_val2";
1208            }
1209        }
1210
1211        assert_eq!(map.get(&1), None);
1212        assert_eq!(map.get(&2), Some(&"modified_val2"));
1213        assert_eq!(map.get(&3), Some(&"val3"));
1214    }
1215}