timed_map/
map.rs

1use super::*;
2
3use crate::iter::{GenericMapIntoIter, GenericMapIter, GenericMapIterMut};
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 = IntoIter<(&'a K, &'a V)>;
245
246    fn into_iter(self) -> Self::IntoIter {
247        let now = self.clock.elapsed_seconds_since_creation();
248        let items: Vec<(&K, &V)> = self
249            .map
250            .iter()
251            .filter_map(|(k, v)| {
252                if !v.is_expired(now) {
253                    Some((k, v.value()))
254                } else {
255                    None
256                }
257            })
258            .collect();
259        items.into_iter()
260    }
261}
262
263impl<'a, K, V, C> IntoIterator for &'a mut TimedMap<K, V, C>
264where
265    K: GenericKey,
266    C: Clock,
267{
268    type Item = (&'a K, &'a mut V);
269    type IntoIter = IntoIter<(&'a K, &'a mut V)>;
270
271    fn into_iter(self) -> Self::IntoIter {
272        let now = self.clock.elapsed_seconds_since_creation();
273        let items: Vec<(&K, &mut V)> = self
274            .map
275            .iter_mut()
276            .filter_map(|(k, v)| {
277                if !v.is_expired(now) {
278                    Some((k, v.value_mut()))
279                } else {
280                    None
281                }
282            })
283            .collect();
284        items.into_iter()
285    }
286}
287
288impl<K, V, C> IntoIterator for TimedMap<K, V, C>
289where
290    K: GenericKey,
291    C: Clock,
292{
293    type Item = (K, V);
294    type IntoIter = IntoIter<(K, V)>;
295
296    fn into_iter(self) -> Self::IntoIter {
297        let now = self.clock.elapsed_seconds_since_creation();
298        let items: Vec<(K, V)> = self
299            .map
300            .into_iter()
301            .filter_map(|(k, v)| {
302                if !v.is_expired(now) {
303                    Some((k.clone(), v.owned_value()))
304                } else {
305                    None
306                }
307            })
308            .collect();
309        items.into_iter()
310    }
311}
312
313impl<K, V, C> Default for TimedMap<K, V, C>
314where
315    C: Default,
316{
317    fn default() -> Self {
318        Self {
319            clock: Default::default(),
320
321            map: GenericMap::default(),
322            expiries: BTreeMap::default(),
323
324            expiration_tick: 0,
325            expiration_tick_cap: 1,
326        }
327    }
328}
329
330#[cfg(feature = "std")]
331impl<K, V> TimedMap<K, V, StdClock>
332where
333    K: GenericKey,
334{
335    /// Creates an empty map.
336    pub fn new() -> Self {
337        Self::default()
338    }
339
340    /// Creates an empty map based on the chosen map implementation specified by `MapKind`.
341    pub fn new_with_map_kind(map_kind: MapKind) -> Self {
342        let map = match map_kind {
343            MapKind::BTreeMap => GenericMap::<K, ExpirableEntry<V>>::BTreeMap(BTreeMap::default()),
344            MapKind::HashMap => GenericMap::HashMap(HashMap::default()),
345            #[cfg(feature = "rustc-hash")]
346            MapKind::FxHashMap => GenericMap::FxHashMap(FxHashMap::default()),
347        };
348
349        Self {
350            map,
351
352            clock: StdClock::default(),
353            expiries: BTreeMap::default(),
354
355            expiration_tick: 0,
356            expiration_tick_cap: 1,
357        }
358    }
359}
360
361impl<K, V, C> TimedMap<K, V, C>
362where
363    C: Clock,
364    K: GenericKey,
365{
366    /// Creates an empty `TimedMap`.
367    ///
368    /// Uses the provided `clock` to handle expiration times.
369    #[cfg(not(feature = "std"))]
370    pub fn new(clock: C) -> Self {
371        Self {
372            clock,
373            map: GenericMap::default(),
374            expiries: BTreeMap::default(),
375            expiration_tick: 0,
376            expiration_tick_cap: 1,
377        }
378    }
379
380    /// Configures `expiration_tick_cap`, which sets how often `TimedMap::drop_expired_entries`
381    /// is automatically called. The default value is 1.
382    ///
383    /// On each insert (excluding `unchecked` ones), an internal counter `expiration_tick` is incremented.
384    /// When `expiration_tick` meets or exceeds `expiration_tick_cap`, `TimedMap::drop_expired_entries` is
385    /// triggered to remove expired entries.
386    ///
387    /// Use this to control cleanup frequency and optimize performance. For example, if your workload
388    /// involves about 100 inserts within couple seconds, setting `expiration_tick_cap` to 100 can improve
389    /// the performance significantly.
390    #[inline(always)]
391    pub fn expiration_tick_cap(mut self, expiration_tick_cap: u16) -> Self {
392        self.expiration_tick_cap = expiration_tick_cap;
393        self
394    }
395
396    /// Returns the associated value if present and not expired.
397    ///
398    /// To retrieve the value without checking expiration, use `TimedMap::get_unchecked`.
399    pub fn get(&self, k: &K) -> Option<&V> {
400        self.map
401            .get(k)
402            .filter(|v| !v.is_expired(self.clock.elapsed_seconds_since_creation()))
403            .map(|v| v.value())
404    }
405
406    /// Returns a mutable reference to the value corresponding to the key.
407    ///
408    /// To retrieve the value without checking expiration, use `TimedMap::get_mut_unchecked`.
409    pub fn get_mut(&mut self, k: &K) -> Option<&mut V> {
410        self.map
411            .get_mut(k)
412            .filter(|v| !v.is_expired(self.clock.elapsed_seconds_since_creation()))
413            .map(|v| v.value_mut())
414    }
415
416    /// Returns the associated value if present, regardless of whether it is expired.
417    ///
418    /// If you only want non-expired entries, use `TimedMap::get` instead.
419    #[inline(always)]
420    pub fn get_unchecked(&self, k: &K) -> Option<&V> {
421        self.map.get(k).map(|v| v.value())
422    }
423
424    /// Returns a mutable reference to the associated value if present, regardless of
425    /// whether it is expired.
426    ///
427    /// If you only want non-expired entries, use `TimedMap::get_mut` instead.
428    #[inline(always)]
429    pub fn get_mut_unchecked(&mut self, k: &K) -> Option<&mut V> {
430        self.map.get_mut(k).map(|v| v.value_mut())
431    }
432
433    /// Returns the associated value's `Duration` if present and not expired.
434    ///
435    /// Returns `None` if the entry does not exist or is constant.
436    pub fn get_remaining_duration(&self, k: &K) -> Option<Duration> {
437        match self.map.get(k) {
438            Some(v) => {
439                let now = self.clock.elapsed_seconds_since_creation();
440                if v.is_expired(now) {
441                    return None;
442                }
443
444                v.remaining_duration(now)
445            }
446            None => None,
447        }
448    }
449
450    /// Returns the number of unexpired elements in the map.
451    ///
452    /// See `TimedMap::len_expired` and `TimedMap::len_unchecked` for other usages.
453    #[inline(always)]
454    pub fn len(&self) -> usize {
455        self.map.len() - self.len_expired()
456    }
457
458    /// Returns the number of expired elements in the map.
459    ///
460    /// See `TimedMap::len` and `TimedMap::len_unchecked` for other usages.
461    #[inline(always)]
462    pub fn len_expired(&self) -> usize {
463        let now = self.clock.elapsed_seconds_since_creation();
464        self.expiries
465            .range(..=now)
466            .map(|(_exp, keys)| keys.len())
467            .sum()
468    }
469
470    /// Returns the total number of elements (including expired ones) in the map.
471    ///
472    /// See `TimedMap::len` and `TimedMap::len_expired` for other usages.
473    #[inline(always)]
474    pub fn len_unchecked(&self) -> usize {
475        self.map.len()
476    }
477
478    /// Returns keys of the map
479    #[inline(always)]
480    pub fn keys(&self) -> Vec<K> {
481        self.map.keys()
482    }
483
484    /// Returns true if the map contains no elements.
485    #[inline(always)]
486    pub fn is_empty(&self) -> bool {
487        self.map.is_empty()
488    }
489
490    /// Inserts a key-value pair with an expiration duration. If duration is `None`,
491    /// entry will be stored in a non-expirable way.
492    ///
493    /// If a value already exists for the given key, it will be updated and then
494    /// the old one will be returned.
495    #[inline(always)]
496    fn insert(&mut self, k: K, v: V, expires_at: Option<u64>) -> Option<V> {
497        let entry = ExpirableEntry::new(v, expires_at);
498        match self.map.insert(k.clone(), entry) {
499            Some(old) => {
500                // Remove the old expiry record
501                if let EntryStatus::ExpiresAtSeconds(e) = old.status() {
502                    self.drop_key_from_expiry(e, &k)
503                }
504
505                Some(old.owned_value())
506            }
507            None => None,
508        }
509    }
510
511    /// Inserts a key-value pair with an expiration duration, and then drops the
512    /// expired entries.
513    ///
514    /// If a value already exists for the given key, it will be updated and then
515    /// the old one will be returned.
516    ///
517    /// If you don't want to the check expired entries, consider using `TimedMap::insert_expirable_unchecked`
518    /// instead.
519    pub fn insert_expirable(&mut self, k: K, v: V, duration: Duration) -> Option<V> {
520        self.expiration_tick += 1;
521
522        let now = self.clock.elapsed_seconds_since_creation();
523        if self.expiration_tick >= self.expiration_tick_cap {
524            self.drop_expired_entries_inner(now);
525            self.expiration_tick = 0;
526        }
527
528        let expires_at = now + duration.as_secs();
529
530        let res = self.insert(k.clone(), v, Some(expires_at));
531
532        self.expiries.entry(expires_at).or_default().insert(k);
533
534        res
535    }
536
537    /// Inserts a key-value pair with an expiration duration, without checking the expired
538    /// entries.
539    ///
540    /// If a value already exists for the given key, it will be updated and then
541    /// the old one will be returned.
542    ///
543    /// If you want to check the expired entries, consider using `TimedMap::insert_expirable`
544    /// instead.
545    pub fn insert_expirable_unchecked(&mut self, k: K, v: V, duration: Duration) -> Option<V> {
546        let now = self.clock.elapsed_seconds_since_creation();
547        let expires_at = now + duration.as_secs();
548
549        let res = self.insert(k.clone(), v, Some(expires_at));
550
551        self.expiries.entry(expires_at).or_default().insert(k);
552
553        res
554    }
555
556    /// Inserts a key-value pair with that doesn't expire, and then drops the
557    /// expired entries.
558    ///
559    /// If a value already exists for the given key, it will be updated and then
560    /// the old one will be returned.
561    ///
562    /// If you don't want to check the expired entries, consider using `TimedMap::insert_constant_unchecked`
563    /// instead.
564    pub fn insert_constant(&mut self, k: K, v: V) -> Option<V> {
565        self.expiration_tick += 1;
566
567        let now = self.clock.elapsed_seconds_since_creation();
568        if self.expiration_tick >= self.expiration_tick_cap {
569            self.drop_expired_entries_inner(now);
570            self.expiration_tick = 0;
571        }
572
573        self.insert(k, v, None)
574    }
575
576    /// Inserts a key-value pair with that doesn't expire without checking the expired
577    /// entries.
578    ///
579    /// If a value already exists for the given key, it will be updated and then
580    /// the old one will be returned.
581    ///
582    /// If you want to check the expired entries, consider using `TimedMap::insert_constant`
583    /// instead.
584    pub fn insert_constant_unchecked(&mut self, k: K, v: V) -> Option<V> {
585        self.expiration_tick += 1;
586        self.insert(k, v, None)
587    }
588
589    /// Removes a key-value pair from the map and returns the associated value if present
590    /// and not expired.
591    ///
592    /// If you want to retrieve the entry after removal even if it is expired, consider using
593    /// `TimedMap::remove_unchecked`.
594    #[inline(always)]
595    pub fn remove(&mut self, k: &K) -> Option<V> {
596        self.map
597            .remove(k)
598            .filter(|v| {
599                if let EntryStatus::ExpiresAtSeconds(expires_at_seconds) = v.status() {
600                    self.drop_key_from_expiry(expires_at_seconds, k);
601                }
602
603                !v.is_expired(self.clock.elapsed_seconds_since_creation())
604            })
605            .map(|v| v.owned_value())
606    }
607
608    /// Removes a key-value pair from the map and returns the associated value if present,
609    /// regardless of expiration status.
610    ///
611    /// If you only want the entry when it is not expired, consider using `TimedMap::remove`.
612    #[inline(always)]
613    pub fn remove_unchecked(&mut self, k: &K) -> Option<V> {
614        self.map
615            .remove(k)
616            .filter(|v| {
617                if let EntryStatus::ExpiresAtSeconds(expires_at_seconds) = v.status() {
618                    self.drop_key_from_expiry(expires_at_seconds, k);
619                }
620
621                true
622            })
623            .map(|v| v.owned_value())
624    }
625
626    /// Clears the map, removing all elements.
627    #[inline(always)]
628    pub fn clear(&mut self) {
629        self.map.clear()
630    }
631
632    /// Returns an iterator over non-expired key-value pairs.
633    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
634        let now = self.clock.elapsed_seconds_since_creation();
635        self.map.iter().filter_map(move |(k, v)| {
636            if !v.is_expired(now) {
637                Some((k, v.value()))
638            } else {
639                None
640            }
641        })
642    }
643
644    /// Returns an iterator over all key-value pairs, including expired ones.
645    pub fn iter_unchecked(&self) -> impl Iterator<Item = (&K, &V)> {
646        self.map.iter().map(|(k, v)| (k, v.value()))
647    }
648
649    /// Returns a mutable iterator over non-expired key-value pairs.
650    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
651        let now = self.clock.elapsed_seconds_since_creation();
652        self.map.iter_mut().filter_map(move |(k, v)| {
653            if !v.is_expired(now) {
654                Some((k, v.value_mut()))
655            } else {
656                None
657            }
658        })
659    }
660
661    /// Returns a mutable iterator over all key-value pairs, including expired ones.
662    pub fn iter_mut_unchecked(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
663        self.map.iter_mut().map(|(k, v)| (k, v.value_mut()))
664    }
665
666    /// Updates the expiration status of an entry and returns the old status.
667    ///
668    /// If the entry does not exist, returns Err.
669    /// If the entry's old status is `EntryStatus::Constant`, returns None.
670    pub fn update_expiration_status(
671        &mut self,
672        key: K,
673        duration: Duration,
674    ) -> Result<Option<EntryStatus>, &'static str> {
675        match self.map.get_mut(&key) {
676            Some(entry) => {
677                let old_status = *entry.status();
678                let now = self.clock.elapsed_seconds_since_creation();
679                let expires_at = now + duration.as_secs();
680
681                entry.update_status(EntryStatus::ExpiresAtSeconds(expires_at));
682
683                if let EntryStatus::ExpiresAtSeconds(t) = &old_status {
684                    self.drop_key_from_expiry(t, &key);
685                }
686                self.expiries
687                    .entry(expires_at)
688                    .or_default()
689                    .insert(key.clone());
690
691                Ok(Some(old_status))
692            }
693            None => Err("entry not found"),
694        }
695    }
696
697    /// Clears expired entries from the map and returns them.
698    ///
699    /// Call this function when using `*_unchecked` inserts, as these do not
700    /// automatically clear expired entries.
701    #[inline(always)]
702    pub fn drop_expired_entries(&mut self) -> Vec<(K, V)> {
703        let now = self.clock.elapsed_seconds_since_creation();
704        self.drop_expired_entries_inner(now)
705    }
706
707    fn drop_expired_entries_inner(&mut self, now: u64) -> Vec<(K, V)> {
708        let mut expired_entries = Vec::new();
709        // Iterates through `expiries` in order and drops expired ones.
710        while let Some((exp, keys)) = self.expiries.pop_first() {
711            // It's safe to do early-break here as keys are sorted by expiration.
712            if exp > now {
713                self.expiries.insert(exp, keys);
714                break;
715            }
716
717            for key in keys {
718                if let Some(value) = self.map.remove(&key) {
719                    expired_entries.push((key, value.owned_value()));
720                }
721            }
722        }
723
724        expired_entries
725    }
726
727    fn drop_key_from_expiry(&mut self, expiry_key: &u64, map_key: &K) {
728        if let Some(list) = self.expiries.get_mut(expiry_key) {
729            list.remove(map_key);
730
731            if list.is_empty() {
732                self.expiries.remove(expiry_key);
733            }
734        }
735    }
736
737    /// Returns `true` if the map contains a non-expired value for the given key.
738    ///
739    /// To include expired entries as well, use [`TimedMap::contains_key_unchecked`].
740    #[inline(always)]
741    pub fn contains_key(&self, k: &K) -> bool {
742        self.get(k).is_some()
743    }
744
745    /// Returns `true` if the map contains a value for the given key regardless of expiration status.
746    ///
747    /// To exclude expired entries, use [`TimedMap::contains_key`] instead.
748    #[inline(always)]
749    pub fn contains_key_unchecked(&self, k: &K) -> bool {
750        self.get_unchecked(k).is_some()
751    }
752}
753
754#[cfg(test)]
755#[cfg(not(feature = "std"))]
756mod tests {
757    use super::*;
758
759    struct MockClock {
760        current_time: u64,
761    }
762
763    impl Clock for MockClock {
764        fn elapsed_seconds_since_creation(&self) -> u64 {
765            self.current_time
766        }
767    }
768
769    #[test]
770    fn nostd_insert_and_get_constant_entry() {
771        let clock = MockClock { current_time: 1000 };
772        let mut map = TimedMap::new(clock);
773
774        map.insert_constant(1, "constant value");
775
776        assert_eq!(map.get(&1), Some(&"constant value"));
777        assert_eq!(map.get_remaining_duration(&1), None);
778    }
779
780    #[test]
781    fn nostd_insert_and_get_expirable_entry() {
782        let clock = MockClock { current_time: 1000 };
783        let mut map = TimedMap::new(clock);
784        let duration = Duration::from_secs(60);
785
786        map.insert_expirable(1, "expirable value", duration);
787
788        assert_eq!(map.get(&1), Some(&"expirable value"));
789        assert_eq!(map.get_remaining_duration(&1), Some(duration));
790    }
791
792    #[test]
793    fn nostd_expired_entry() {
794        let clock = MockClock { current_time: 1000 };
795        let mut map = TimedMap::new(clock);
796        let duration = Duration::from_secs(60);
797
798        // Insert entry that expires in 60 seconds
799        map.insert_expirable(1, "expirable value", duration);
800
801        // Simulate time passage beyond expiration
802        let clock = MockClock { current_time: 1070 };
803        map.clock = clock;
804
805        // The entry should be considered expired
806        assert_eq!(map.get(&1), None);
807        assert_eq!(map.get_remaining_duration(&1), None);
808    }
809
810    #[test]
811    fn nostd_remove_entry() {
812        let clock = MockClock { current_time: 1000 };
813        let mut map = TimedMap::new(clock);
814
815        map.insert_constant(1, "constant value");
816
817        assert_eq!(map.remove(&1), Some("constant value"));
818        assert_eq!(map.get(&1), None);
819    }
820
821    #[test]
822    fn nostd_drop_expired_entries() {
823        let clock = MockClock { current_time: 1000 };
824        let mut map = TimedMap::new(clock);
825
826        // Insert one constant and 2 expirable entries
827        map.insert_expirable(1, "expirable value1", Duration::from_secs(50));
828        map.insert_expirable(2, "expirable value2", Duration::from_secs(70));
829        map.insert_constant(3, "constant value");
830
831        // Simulate time passage beyond the expiration of the first entry
832        let clock = MockClock { current_time: 1055 };
833        map.clock = clock;
834
835        // Entry 1 should be removed and entry 2 and 3 should still exist
836        assert_eq!(map.get(&1), None);
837        assert_eq!(map.get(&2), Some(&"expirable value2"));
838        assert_eq!(map.get(&3), Some(&"constant value"));
839
840        // Simulate time passage again to expire second expirable entry
841        let clock = MockClock { current_time: 1071 };
842        map.clock = clock;
843
844        assert_eq!(map.get(&1), None);
845        assert_eq!(map.get(&2), None);
846        assert_eq!(map.get(&3), Some(&"constant value"));
847    }
848
849    #[test]
850    fn nostd_update_existing_entry() {
851        let clock = MockClock { current_time: 1000 };
852        let mut map = TimedMap::new(clock);
853
854        map.insert_constant(1, "initial value");
855        assert_eq!(map.get(&1), Some(&"initial value"));
856
857        // Update the value of the existing key and make it expirable
858        map.insert_expirable(1, "updated value", Duration::from_secs(15));
859        assert_eq!(map.get(&1), Some(&"updated value"));
860
861        // Simulate time passage and expire the updated entry
862        let clock = MockClock { current_time: 1016 };
863        map.clock = clock;
864
865        assert_eq!(map.get(&1), None);
866    }
867
868    #[test]
869    fn nostd_update_expirable_entry_status() {
870        let clock = MockClock { current_time: 1000 };
871        let mut map = TimedMap::new(clock);
872
873        map.insert_constant(1, "initial value");
874        assert_eq!(map.get(&1), Some(&"initial value"));
875
876        // Update the value of the existing key and make it expirable
877        map.update_expiration_status(1, Duration::from_secs(16))
878            .expect("entry update shouldn't fail");
879        assert_eq!(map.get(&1), Some(&"initial value"));
880
881        // Simulate time passage and expire the updated entry
882        let clock = MockClock { current_time: 1017 };
883        map.clock = clock;
884        assert_eq!(map.get(&1), None);
885    }
886
887    #[test]
888    fn nostd_update_expirable_entry_status_with_previou_time() {
889        let clock = MockClock { current_time: 1000 };
890        let mut map = TimedMap::new(clock);
891
892        // Insert map entry followed by immediately updating expiration time
893        map.insert_expirable(1, "expirable value", Duration::from_secs(15));
894        map.update_expiration_status(1, Duration::from_secs(15))
895            .expect("entry update shouldn't fail");
896
897        // We should still have our entry.
898        assert_eq!(map.get(&1), Some(&"expirable value"));
899        assert!(map.expiries.contains_key(&1015));
900    }
901
902    #[test]
903    fn nostd_contains_key() {
904        let clock = MockClock { current_time: 1000 };
905        let mut map = TimedMap::new(clock);
906
907        // Insert map entry and check if exists
908        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
909        assert!(map.contains_key(&1));
910    }
911}
912
913#[cfg(feature = "std")]
914#[cfg(test)]
915mod std_tests {
916    use core::ops::Add;
917
918    use super::*;
919
920    #[test]
921    fn std_expirable_and_constant_entries() {
922        let mut map = TimedMap::new();
923
924        map.insert_constant(1, "constant value");
925        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
926
927        assert_eq!(map.get(&1), Some(&"constant value"));
928        assert_eq!(map.get(&2), Some(&"expirable value"));
929
930        assert_eq!(map.get_remaining_duration(&1), None);
931        assert!(map.get_remaining_duration(&2).is_some());
932    }
933
934    #[test]
935    fn std_expired_entry_removal() {
936        let mut map = TimedMap::new();
937        let duration = Duration::from_secs(2);
938
939        map.insert_expirable(1, "expirable value", duration);
940
941        // Wait for expiration
942        std::thread::sleep(Duration::from_secs(3));
943
944        // Entry should now be expired
945        assert_eq!(map.get(&1), None);
946        assert_eq!(map.get_remaining_duration(&1), None);
947    }
948
949    #[test]
950    fn std_remove_entry() {
951        let mut map = TimedMap::new();
952
953        map.insert_constant(1, "constant value");
954        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
955
956        assert_eq!(map.remove(&1), Some("constant value"));
957        assert_eq!(map.remove(&2), Some("expirable value"));
958
959        assert_eq!(map.get(&1), None);
960        assert_eq!(map.get(&2), None);
961    }
962
963    #[test]
964    fn std_drop_expired_entries() {
965        let mut map = TimedMap::new();
966
967        map.insert_expirable(1, "expirable value1", Duration::from_secs(2));
968        map.insert_expirable(2, "expirable value2", Duration::from_secs(4));
969
970        // Wait for expiration
971        std::thread::sleep(Duration::from_secs(3));
972
973        // Entry 1 should be removed and entry 2 should still exist
974        assert_eq!(map.get(&1), None);
975        assert_eq!(map.get(&2), Some(&"expirable value2"));
976    }
977
978    #[test]
979    fn std_update_existing_entry() {
980        let mut map = TimedMap::new();
981
982        map.insert_constant(1, "initial value");
983        assert_eq!(map.get(&1), Some(&"initial value"));
984
985        // Update the value of the existing key and make it expirable
986        map.insert_expirable(1, "updated value", Duration::from_secs(1));
987        assert_eq!(map.get(&1), Some(&"updated value"));
988
989        std::thread::sleep(Duration::from_secs(2));
990
991        // Should be expired now
992        assert_eq!(map.get(&1), None);
993    }
994
995    #[test]
996    fn std_insert_constant_and_expirable_combined() {
997        let mut map = TimedMap::new();
998
999        // Insert a constant entry and an expirable entry
1000        map.insert_constant(1, "constant value");
1001        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
1002
1003        // Check both entries exist
1004        assert_eq!(map.get(&1), Some(&"constant value"));
1005        assert_eq!(map.get(&2), Some(&"expirable value"));
1006
1007        // Simulate passage of time beyond expiration
1008        std::thread::sleep(Duration::from_secs(3));
1009
1010        // Constant entry should still exist, expirable should be expired
1011        assert_eq!(map.get(&1), Some(&"constant value"));
1012        assert_eq!(map.get(&2), None);
1013    }
1014
1015    #[test]
1016    fn std_expirable_entry_still_valid_before_expiration() {
1017        let mut map = TimedMap::new();
1018
1019        // Insert an expirable entry with a duration of 3 seconds
1020        map.insert_expirable(1, "expirable value", Duration::from_secs(3));
1021
1022        // Simulate a short sleep of 2 seconds (still valid)
1023        std::thread::sleep(Duration::from_secs(2));
1024
1025        // The entry should still be valid
1026        assert_eq!(map.get(&1), Some(&"expirable value"));
1027        assert!(map.get_remaining_duration(&1).unwrap().as_secs() == 1);
1028    }
1029
1030    #[test]
1031    fn std_length_functions() {
1032        let mut map = TimedMap::new();
1033
1034        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1035        map.insert_expirable(2, "expirable value", Duration::from_secs(1));
1036        map.insert_expirable(3, "expirable value", Duration::from_secs(3));
1037        map.insert_expirable(4, "expirable value", Duration::from_secs(3));
1038        map.insert_expirable(5, "expirable value", Duration::from_secs(3));
1039        map.insert_expirable(6, "expirable value", Duration::from_secs(3));
1040
1041        std::thread::sleep(Duration::from_secs(2).add(Duration::from_millis(1)));
1042
1043        assert_eq!(map.len(), 4);
1044        assert_eq!(map.len_expired(), 2);
1045        assert_eq!(map.len_unchecked(), 6);
1046    }
1047
1048    #[test]
1049    fn std_update_expirable_entry() {
1050        let mut map = TimedMap::new();
1051
1052        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1053        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
1054
1055        std::thread::sleep(Duration::from_secs(2));
1056
1057        assert!(!map.expiries.contains_key(&1));
1058        assert!(map.expiries.contains_key(&5));
1059        assert_eq!(map.get(&1), Some(&"expirable value"));
1060    }
1061
1062    #[test]
1063    fn std_update_expirable_entry_status() {
1064        let mut map = TimedMap::new();
1065
1066        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1067        map.update_expiration_status(1, Duration::from_secs(5))
1068            .expect("entry update shouldn't fail");
1069
1070        std::thread::sleep(Duration::from_secs(3));
1071        assert!(!map.expiries.contains_key(&1));
1072        assert!(map.expiries.contains_key(&5));
1073        assert_eq!(map.get(&1), Some(&"expirable value"));
1074    }
1075
1076    #[test]
1077    fn std_update_expirable_entry_status_with_previou_time() {
1078        let mut map = TimedMap::new();
1079
1080        // Insert map entry followed by immediately updating expiration time
1081        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
1082        map.update_expiration_status(1, Duration::from_secs(5))
1083            .expect("entry update shouldn't fail");
1084
1085        // We should still have our entry.
1086        assert_eq!(map.get(&1), Some(&"expirable value"));
1087        assert!(map.expiries.contains_key(&5));
1088    }
1089
1090    #[test]
1091    fn std_contains_key() {
1092        let mut map = TimedMap::new();
1093
1094        // Insert map entry and check if exists
1095        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1096        assert!(map.contains_key(&1));
1097    }
1098
1099    #[test]
1100    fn std_does_not_contain_key_anymore() {
1101        let mut map = TimedMap::new();
1102
1103        // Insert map entry and check if still exists after expiry
1104        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1105        std::thread::sleep(Duration::from_secs(2));
1106        assert!(!map.contains_key(&1));
1107    }
1108
1109    #[test]
1110    fn std_contains_key_unchecked() {
1111        let mut map = TimedMap::new();
1112
1113        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1114        std::thread::sleep(Duration::from_secs(2));
1115        assert!(map.contains_key_unchecked(&1));
1116    }
1117
1118    #[test]
1119    #[cfg(feature = "serde")]
1120    fn ignore_expired_entries_on_serialize() {
1121        let mut map = TimedMap::new();
1122
1123        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1124        map.insert_expirable(2, "expirable value2", Duration::from_secs(5));
1125        std::thread::sleep(Duration::from_secs(2));
1126
1127        let actual = serde_json::json!(map);
1128        let expected = serde_json::json!({
1129            "2": "expirable value2"
1130        });
1131
1132        assert_eq!(expected, actual);
1133    }
1134}