Skip to main content

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 for non-expired entries.
455    ///
456    /// To include expired entries as well, use [`TimedMap::keys_unchecked`].
457    #[inline(always)]
458    pub fn keys(&self) -> Vec<K> {
459        let now = self.clock.elapsed_seconds_since_creation();
460        self.map
461            .iter()
462            .filter(|(_k, v)| !v.is_expired(now))
463            .map(|(k, _v)| k.clone())
464            .collect()
465    }
466
467    /// Returns keys for expired entries.
468    #[inline(always)]
469    pub fn keys_expired(&self) -> Vec<K> {
470        let now = self.clock.elapsed_seconds_since_creation();
471        self.map
472            .iter()
473            .filter(|(_k, v)| v.is_expired(now))
474            .map(|(k, _v)| k.clone())
475            .collect()
476    }
477
478    /// Returns keys for all entries, including expired ones.
479    ///
480    /// To exclude expired entries, use [`TimedMap::keys`] instead.
481    #[inline(always)]
482    pub fn keys_unchecked(&self) -> Vec<K> {
483        self.map.keys()
484    }
485
486    /// Returns true if the map contains no non-expired elements.
487    ///
488    /// To include expired entries as well, use [`TimedMap::is_empty_unchecked`].
489    #[inline(always)]
490    pub fn is_empty(&self) -> bool {
491        self.len() == 0
492    }
493
494    /// Returns true if the map contains no elements, regardless of expiration status.
495    ///
496    /// To exclude expired entries, use [`TimedMap::is_empty`] instead.
497    #[inline(always)]
498    pub fn is_empty_unchecked(&self) -> bool {
499        self.map.is_empty()
500    }
501
502    /// Inserts a key-value pair with an expiration duration. If duration is `None`,
503    /// entry will be stored in a non-expirable way.
504    ///
505    /// If a value already exists for the given key, it will be updated and then
506    /// the old one will be returned.
507    #[inline(always)]
508    fn insert(&mut self, k: K, v: V, expires_at: Option<u64>) -> Option<V> {
509        let entry = ExpirableEntry::new(v, expires_at);
510        match self.map.insert(k.clone(), entry) {
511            Some(old) => {
512                // Remove the old expiry record
513                if let EntryStatus::ExpiresAtSeconds(e) = old.status() {
514                    self.drop_key_from_expiry(e, &k)
515                }
516
517                Some(old.owned_value())
518            }
519            None => None,
520        }
521    }
522
523    /// Inserts a key-value pair with an expiration duration, and then drops the
524    /// expired entries.
525    ///
526    /// If a value already exists for the given key, it will be updated and then
527    /// the old one will be returned.
528    ///
529    /// If you don't want to the check expired entries, consider using `TimedMap::insert_expirable_unchecked`
530    /// instead.
531    pub fn insert_expirable(&mut self, k: K, v: V, duration: Duration) -> Option<V> {
532        self.expiration_tick += 1;
533
534        let now = self.clock.elapsed_seconds_since_creation();
535        if self.expiration_tick >= self.expiration_tick_cap {
536            self.drop_expired_entries_inner(now);
537            self.expiration_tick = 0;
538        }
539
540        let expires_at = now + duration.as_secs();
541
542        let res = self.insert(k.clone(), v, Some(expires_at));
543
544        self.expiries.entry(expires_at).or_default().insert(k);
545
546        res
547    }
548
549    /// Inserts a key-value pair with an expiration duration, without checking the expired
550    /// entries.
551    ///
552    /// If a value already exists for the given key, it will be updated and then
553    /// the old one will be returned.
554    ///
555    /// If you want to check the expired entries, consider using `TimedMap::insert_expirable`
556    /// instead.
557    pub fn insert_expirable_unchecked(&mut self, k: K, v: V, duration: Duration) -> Option<V> {
558        let now = self.clock.elapsed_seconds_since_creation();
559        let expires_at = now + duration.as_secs();
560
561        let res = self.insert(k.clone(), v, Some(expires_at));
562
563        self.expiries.entry(expires_at).or_default().insert(k);
564
565        res
566    }
567
568    /// Inserts a key-value pair with that doesn't expire, and then drops the
569    /// expired entries.
570    ///
571    /// If a value already exists for the given key, it will be updated and then
572    /// the old one will be returned.
573    ///
574    /// If you don't want to check the expired entries, consider using `TimedMap::insert_constant_unchecked`
575    /// instead.
576    pub fn insert_constant(&mut self, k: K, v: V) -> Option<V> {
577        self.expiration_tick += 1;
578
579        let now = self.clock.elapsed_seconds_since_creation();
580        if self.expiration_tick >= self.expiration_tick_cap {
581            self.drop_expired_entries_inner(now);
582            self.expiration_tick = 0;
583        }
584
585        self.insert(k, v, None)
586    }
587
588    /// Inserts a key-value pair with that doesn't expire without checking the expired
589    /// entries.
590    ///
591    /// If a value already exists for the given key, it will be updated and then
592    /// the old one will be returned.
593    ///
594    /// If you want to check the expired entries, consider using `TimedMap::insert_constant`
595    /// instead.
596    pub fn insert_constant_unchecked(&mut self, k: K, v: V) -> Option<V> {
597        self.expiration_tick += 1;
598        self.insert(k, v, None)
599    }
600
601    /// Removes a key-value pair from the map and returns the associated value if present
602    /// and not expired.
603    ///
604    /// If you want to retrieve the entry after removal even if it is expired, consider using
605    /// `TimedMap::remove_unchecked`.
606    #[inline(always)]
607    pub fn remove(&mut self, k: &K) -> Option<V> {
608        self.map
609            .remove(k)
610            .filter(|v| {
611                if let EntryStatus::ExpiresAtSeconds(expires_at_seconds) = v.status() {
612                    self.drop_key_from_expiry(expires_at_seconds, k);
613                }
614
615                !v.is_expired(self.clock.elapsed_seconds_since_creation())
616            })
617            .map(|v| v.owned_value())
618    }
619
620    /// Removes a key-value pair from the map and returns the associated value if present,
621    /// regardless of expiration status.
622    ///
623    /// If you only want the entry when it is not expired, consider using `TimedMap::remove`.
624    #[inline(always)]
625    pub fn remove_unchecked(&mut self, k: &K) -> Option<V> {
626        self.map
627            .remove(k)
628            .filter(|v| {
629                if let EntryStatus::ExpiresAtSeconds(expires_at_seconds) = v.status() {
630                    self.drop_key_from_expiry(expires_at_seconds, k);
631                }
632
633                true
634            })
635            .map(|v| v.owned_value())
636    }
637
638    /// Clears the map, removing all elements.
639    #[inline(always)]
640    pub fn clear(&mut self) {
641        self.map.clear();
642        self.expiries.clear();
643    }
644
645    /// Returns an iterator over non-expired key-value pairs.
646    pub fn iter(&self) -> Iter<'_, K, V> {
647        self.into_iter()
648    }
649
650    /// Returns an iterator over all key-value pairs, including expired ones.
651    pub fn iter_unchecked(&self) -> impl Iterator<Item = (&K, &V)> {
652        self.map.iter().map(|(k, v)| (k, v.value()))
653    }
654
655    /// Returns a mutable iterator over non-expired key-value pairs.
656    pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
657        self.into_iter()
658    }
659
660    /// Returns a mutable iterator over all key-value pairs, including expired ones.
661    pub fn iter_mut_unchecked(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
662        self.map.iter_mut().map(|(k, v)| (k, v.value_mut()))
663    }
664
665    /// Updates the expiration status of an entry and returns the old expirable status.
666    ///
667    /// If the entry does not exist, returns Err.
668    /// If the entry's old status is `EntryStatus::Constant`, returns None.
669    pub fn update_expiration_status(
670        &mut self,
671        key: K,
672        duration: Duration,
673    ) -> Result<Option<EntryStatus>, &'static str> {
674        match self.map.get_mut(&key) {
675            Some(entry) => {
676                let old_status = *entry.status();
677                let now = self.clock.elapsed_seconds_since_creation();
678                let expires_at = now + duration.as_secs();
679
680                entry.update_status(EntryStatus::ExpiresAtSeconds(expires_at));
681
682                if let EntryStatus::ExpiresAtSeconds(t) = &old_status {
683                    self.drop_key_from_expiry(t, &key);
684                }
685                self.expiries
686                    .entry(expires_at)
687                    .or_default()
688                    .insert(key.clone());
689
690                match old_status {
691                    EntryStatus::Constant => Ok(None),
692                    EntryStatus::ExpiresAtSeconds(_) => Ok(Some(old_status)),
693                }
694            }
695            None => Err("entry not found"),
696        }
697    }
698
699    /// Clears expired entries from the map and returns them.
700    ///
701    /// Call this function when using `*_unchecked` inserts, as these do not
702    /// automatically clear expired entries.
703    #[inline(always)]
704    pub fn drop_expired_entries(&mut self) -> Vec<(K, V)> {
705        let now = self.clock.elapsed_seconds_since_creation();
706        self.drop_expired_entries_inner(now)
707    }
708
709    fn drop_expired_entries_inner(&mut self, now: u64) -> Vec<(K, V)> {
710        let mut expired_entries = Vec::new();
711        // Iterates through `expiries` in order and drops expired ones.
712        while let Some((exp, keys)) = self.expiries.pop_first() {
713            // It's safe to do early-break here as keys are sorted by expiration.
714            if exp > now {
715                self.expiries.insert(exp, keys);
716                break;
717            }
718
719            for key in keys {
720                if let Some(value) = self.map.remove(&key) {
721                    expired_entries.push((key, value.owned_value()));
722                }
723            }
724        }
725
726        expired_entries
727    }
728
729    fn drop_key_from_expiry(&mut self, expiry_key: &u64, map_key: &K) {
730        if let Some(list) = self.expiries.get_mut(expiry_key) {
731            list.remove(map_key);
732
733            if list.is_empty() {
734                self.expiries.remove(expiry_key);
735            }
736        }
737    }
738
739    /// Returns `true` if the map contains a non-expired value for the given key.
740    ///
741    /// To include expired entries as well, use [`TimedMap::contains_key_unchecked`].
742    #[inline(always)]
743    pub fn contains_key(&self, k: &K) -> bool {
744        self.get(k).is_some()
745    }
746
747    /// Returns `true` if the map contains a value for the given key regardless of expiration status.
748    ///
749    /// To exclude expired entries, use [`TimedMap::contains_key`] instead.
750    #[inline(always)]
751    pub fn contains_key_unchecked(&self, k: &K) -> bool {
752        self.get_unchecked(k).is_some()
753    }
754}
755
756#[cfg(test)]
757#[cfg(not(feature = "std"))]
758mod tests {
759    use super::*;
760
761    #[derive(Clone, Copy)]
762    struct MockClock {
763        current_time: u64,
764    }
765
766    impl Clock for MockClock {
767        fn elapsed_seconds_since_creation(&self) -> u64 {
768            self.current_time
769        }
770    }
771
772    #[test]
773    fn nostd_insert_and_get_constant_entry() {
774        let clock = MockClock { current_time: 1000 };
775        let mut map = TimedMap::new(clock);
776
777        map.insert_constant(1, "constant value");
778
779        assert_eq!(map.get(&1), Some(&"constant value"));
780        assert_eq!(map.get_remaining_duration(&1), None);
781    }
782
783    #[test]
784    fn nostd_insert_and_get_expirable_entry() {
785        let clock = MockClock { current_time: 1000 };
786        let mut map = TimedMap::new(clock);
787        let duration = Duration::from_secs(60);
788
789        map.insert_expirable(1, "expirable value", duration);
790
791        assert_eq!(map.get(&1), Some(&"expirable value"));
792        assert_eq!(map.get_remaining_duration(&1), Some(duration));
793    }
794
795    #[test]
796    fn nostd_expired_entry() {
797        let clock = MockClock { current_time: 1000 };
798        let mut map = TimedMap::new(clock);
799        let duration = Duration::from_secs(60);
800
801        // Insert entry that expires in 60 seconds
802        map.insert_expirable(1, "expirable value", duration);
803
804        // Simulate time passage beyond expiration
805        let clock = MockClock { current_time: 1070 };
806        map.clock = clock;
807
808        // The entry should be considered expired
809        assert_eq!(map.get(&1), None);
810        assert_eq!(map.get_remaining_duration(&1), None);
811    }
812
813    #[test]
814    fn nostd_remove_entry() {
815        let clock = MockClock { current_time: 1000 };
816        let mut map = TimedMap::new(clock);
817
818        map.insert_constant(1, "constant value");
819
820        assert_eq!(map.remove(&1), Some("constant value"));
821        assert_eq!(map.get(&1), None);
822    }
823
824    #[test]
825    fn nostd_clear_removes_expiry_bookkeeping() {
826        let clock = MockClock { current_time: 1000 };
827        let mut map = TimedMap::new(clock);
828
829        map.insert_expirable(1, "expirable value", Duration::from_secs(60));
830
831        assert!(!map.expiries.is_empty());
832
833        map.clear();
834
835        assert!(map.expiries.is_empty());
836        assert_eq!(map.len(), 0);
837        assert_eq!(map.len_expired(), 0);
838        assert_eq!(map.len_unchecked(), 0);
839    }
840
841    #[test]
842    fn nostd_drop_expired_entries() {
843        let clock = MockClock { current_time: 1000 };
844        let mut map = TimedMap::new(clock);
845
846        // Insert one constant and 2 expirable entries
847        map.insert_expirable(1, "expirable value1", Duration::from_secs(50));
848        map.insert_expirable(2, "expirable value2", Duration::from_secs(70));
849        map.insert_constant(3, "constant value");
850
851        // Simulate time passage beyond the expiration of the first entry
852        let clock = MockClock { current_time: 1055 };
853        map.clock = clock;
854
855        // Entry 1 should be removed and entry 2 and 3 should still exist
856        assert_eq!(map.get(&1), None);
857        assert_eq!(map.get(&2), Some(&"expirable value2"));
858        assert_eq!(map.get(&3), Some(&"constant value"));
859
860        // Simulate time passage again to expire second expirable entry
861        let clock = MockClock { current_time: 1071 };
862        map.clock = clock;
863
864        assert_eq!(map.get(&1), None);
865        assert_eq!(map.get(&2), None);
866        assert_eq!(map.get(&3), Some(&"constant value"));
867    }
868
869    #[test]
870    fn nostd_update_existing_entry() {
871        let clock = MockClock { current_time: 1000 };
872        let mut map = TimedMap::new(clock);
873
874        map.insert_constant(1, "initial value");
875        assert_eq!(map.get(&1), Some(&"initial value"));
876
877        // Update the value of the existing key and make it expirable
878        map.insert_expirable(1, "updated value", Duration::from_secs(15));
879        assert_eq!(map.get(&1), Some(&"updated value"));
880
881        // Simulate time passage and expire the updated entry
882        let clock = MockClock { current_time: 1016 };
883        map.clock = clock;
884
885        assert_eq!(map.get(&1), None);
886    }
887
888    #[test]
889    fn nostd_update_expirable_entry_status() {
890        let clock = MockClock { current_time: 1000 };
891        let mut map = TimedMap::new(clock);
892
893        map.insert_constant(1, "initial value");
894        assert_eq!(map.get(&1), Some(&"initial value"));
895
896        // Update the value of the existing key and make it expirable
897        let old_status = map
898            .update_expiration_status(1, Duration::from_secs(16))
899            .expect("entry update shouldn't fail");
900        assert!(old_status.is_none());
901        assert_eq!(map.get(&1), Some(&"initial value"));
902
903        // Simulate time passage and expire the updated entry
904        let clock = MockClock { current_time: 1017 };
905        map.clock = clock;
906        assert_eq!(map.get(&1), None);
907    }
908
909    #[test]
910    fn nostd_update_expirable_entry_status_with_previou_time() {
911        let clock = MockClock { current_time: 1000 };
912        let mut map = TimedMap::new(clock);
913
914        // Insert map entry followed by immediately updating expiration time
915        map.insert_expirable(1, "expirable value", Duration::from_secs(15));
916        let old_status = map
917            .update_expiration_status(1, Duration::from_secs(15))
918            .expect("entry update shouldn't fail");
919        assert!(matches!(
920            old_status,
921            Some(EntryStatus::ExpiresAtSeconds(1015))
922        ));
923
924        // We should still have our entry.
925        assert_eq!(map.get(&1), Some(&"expirable value"));
926        assert!(map.expiries.contains_key(&1015));
927    }
928
929    #[test]
930    fn nostd_contains_key() {
931        let clock = MockClock { current_time: 1000 };
932        let mut map = TimedMap::new(clock);
933
934        // Insert map entry and check if exists
935        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
936        assert!(map.contains_key(&1));
937    }
938
939    #[test]
940    fn nostd_keys_and_is_empty_ignore_expired_entries() {
941        let mut clock = MockClock { current_time: 1000 };
942        let mut map = TimedMap::new(clock);
943
944        map.insert_expirable(1, "expired value", Duration::from_secs(1));
945        map.insert_constant(2, "constant value");
946
947        clock.current_time = 1002;
948        map.clock = clock;
949
950        assert_eq!(map.keys().as_slice(), &[2]);
951        assert_eq!(map.keys_expired().as_slice(), &[1]);
952        assert_eq!(map.keys_unchecked().as_slice(), &[1, 2]);
953        assert!(!map.is_empty());
954        assert!(!map.is_empty_unchecked());
955
956        map.remove(&2);
957
958        assert!(map.is_empty());
959        assert_eq!(map.keys_expired().as_slice(), &[1]);
960        assert!(!map.is_empty_unchecked());
961    }
962
963    #[test]
964    fn nostd_iter_empty() {
965        let clock = MockClock { current_time: 1000 };
966        let map: TimedMap<u64, &str, MockClock> = TimedMap::new(clock);
967        assert_eq!(map.iter().count(), 0);
968        assert_eq!(map.into_iter().count(), 0);
969    }
970
971    #[test]
972    fn nostd_iter_all_expired() {
973        let mut clock = MockClock { current_time: 1000 };
974        let mut map = TimedMap::new(clock);
975
976        // Expires at 1001
977        map.insert_expirable(1, "val1", Duration::from_secs(1));
978        // Expires at 1002
979        map.insert_expirable(2, "val2", Duration::from_secs(2));
980
981        clock.current_time = 1003;
982        map.clock = clock;
983
984        assert_eq!(map.iter().count(), 0);
985        assert_eq!(map.into_iter().count(), 0);
986    }
987
988    #[test]
989    fn nostd_iter_mixed() {
990        let mut clock = MockClock { current_time: 1000 };
991        let mut map = TimedMap::new(clock);
992
993        // Expires at 1001
994        map.insert_expirable(1, "val1", Duration::from_secs(1));
995        // Never expires
996        map.insert_constant(2, "val2");
997        // Expires at 1005
998        map.insert_expirable(3, "val3", Duration::from_secs(5));
999
1000        clock.current_time = 1002;
1001        map.clock = clock;
1002
1003        let mut collected: Vec<(&u64, &&str)> = map.iter().collect();
1004        collected.sort_by_key(|(k, _)| *k);
1005
1006        assert_eq!(collected.len(), 2);
1007        assert_eq!(collected[0], (&2, &"val2"));
1008        assert_eq!(collected[1], (&3, &"val3"));
1009    }
1010
1011    #[test]
1012    fn nostd_iter_mut_mixed() {
1013        let mut clock = MockClock { current_time: 1000 };
1014        let mut map = TimedMap::new(clock);
1015
1016        // Expires at 1001
1017        map.insert_expirable(1, "val1", Duration::from_secs(1));
1018        // Never expires
1019        map.insert_constant(2, "val2");
1020        // Expires at 1005
1021        map.insert_expirable(3, "val3", Duration::from_secs(5));
1022
1023        clock.current_time = 1002;
1024        map.clock = clock;
1025
1026        for (k, v) in map.iter_mut() {
1027            if *k == 2 {
1028                *v = "modified_val2";
1029            }
1030        }
1031
1032        assert_eq!(map.get(&1), None);
1033        assert_eq!(map.get(&2), Some(&"modified_val2"));
1034        assert_eq!(map.get(&3), Some(&"val3"));
1035    }
1036}
1037
1038#[cfg(feature = "std")]
1039#[cfg(test)]
1040mod std_tests {
1041    use core::ops::Add;
1042
1043    use super::*;
1044
1045    #[test]
1046    fn std_expirable_and_constant_entries() {
1047        let mut map = TimedMap::new();
1048
1049        map.insert_constant(1, "constant value");
1050        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
1051
1052        assert_eq!(map.get(&1), Some(&"constant value"));
1053        assert_eq!(map.get(&2), Some(&"expirable value"));
1054
1055        assert_eq!(map.get_remaining_duration(&1), None);
1056        assert!(map.get_remaining_duration(&2).is_some());
1057    }
1058
1059    #[test]
1060    fn std_expired_entry_removal() {
1061        let mut map = TimedMap::new();
1062        let duration = Duration::from_secs(2);
1063
1064        map.insert_expirable(1, "expirable value", duration);
1065
1066        // Wait for expiration
1067        std::thread::sleep(Duration::from_secs(3));
1068
1069        // Entry should now be expired
1070        assert_eq!(map.get(&1), None);
1071        assert_eq!(map.get_remaining_duration(&1), None);
1072    }
1073
1074    #[test]
1075    fn std_remove_entry() {
1076        let mut map = TimedMap::new();
1077
1078        map.insert_constant(1, "constant value");
1079        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
1080
1081        assert_eq!(map.remove(&1), Some("constant value"));
1082        assert_eq!(map.remove(&2), Some("expirable value"));
1083
1084        assert_eq!(map.get(&1), None);
1085        assert_eq!(map.get(&2), None);
1086    }
1087
1088    #[test]
1089    fn std_clear_removes_expiry_bookkeeping() {
1090        let mut map = TimedMap::new();
1091
1092        map.insert_expirable(1, "expirable value", Duration::from_secs(60));
1093
1094        assert!(!map.expiries.is_empty());
1095
1096        map.clear();
1097
1098        assert!(map.expiries.is_empty());
1099        assert_eq!(map.len(), 0);
1100        assert_eq!(map.len_expired(), 0);
1101        assert_eq!(map.len_unchecked(), 0);
1102    }
1103
1104    #[test]
1105    fn std_drop_expired_entries() {
1106        let mut map = TimedMap::new();
1107
1108        map.insert_expirable(1, "expirable value1", Duration::from_secs(2));
1109        map.insert_expirable(2, "expirable value2", Duration::from_secs(4));
1110
1111        // Wait for expiration
1112        std::thread::sleep(Duration::from_secs(3));
1113
1114        // Entry 1 should be removed and entry 2 should still exist
1115        assert_eq!(map.get(&1), None);
1116        assert_eq!(map.get(&2), Some(&"expirable value2"));
1117    }
1118
1119    #[test]
1120    fn std_update_existing_entry() {
1121        let mut map = TimedMap::new();
1122
1123        map.insert_constant(1, "initial value");
1124        assert_eq!(map.get(&1), Some(&"initial value"));
1125
1126        // Update the value of the existing key and make it expirable
1127        map.insert_expirable(1, "updated value", Duration::from_secs(1));
1128        assert_eq!(map.get(&1), Some(&"updated value"));
1129
1130        std::thread::sleep(Duration::from_secs(2));
1131
1132        // Should be expired now
1133        assert_eq!(map.get(&1), None);
1134    }
1135
1136    #[test]
1137    fn std_insert_constant_and_expirable_combined() {
1138        let mut map = TimedMap::new();
1139
1140        // Insert a constant entry and an expirable entry
1141        map.insert_constant(1, "constant value");
1142        map.insert_expirable(2, "expirable value", Duration::from_secs(2));
1143
1144        // Check both entries exist
1145        assert_eq!(map.get(&1), Some(&"constant value"));
1146        assert_eq!(map.get(&2), Some(&"expirable value"));
1147
1148        // Simulate passage of time beyond expiration
1149        std::thread::sleep(Duration::from_secs(3));
1150
1151        // Constant entry should still exist, expirable should be expired
1152        assert_eq!(map.get(&1), Some(&"constant value"));
1153        assert_eq!(map.get(&2), None);
1154    }
1155
1156    #[test]
1157    fn std_expirable_entry_still_valid_before_expiration() {
1158        let mut map = TimedMap::new();
1159
1160        // Insert an expirable entry with a duration of 3 seconds
1161        map.insert_expirable(1, "expirable value", Duration::from_secs(3));
1162
1163        // Simulate a short sleep of 2 seconds (still valid)
1164        std::thread::sleep(Duration::from_secs(2));
1165
1166        // The entry should still be valid
1167        assert_eq!(map.get(&1), Some(&"expirable value"));
1168        assert!(map.get_remaining_duration(&1).unwrap().as_secs() == 1);
1169    }
1170
1171    #[test]
1172    fn std_length_functions() {
1173        let mut map = TimedMap::new();
1174
1175        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1176        map.insert_expirable(2, "expirable value", Duration::from_secs(1));
1177        map.insert_expirable(3, "expirable value", Duration::from_secs(3));
1178        map.insert_expirable(4, "expirable value", Duration::from_secs(3));
1179        map.insert_expirable(5, "expirable value", Duration::from_secs(3));
1180        map.insert_expirable(6, "expirable value", Duration::from_secs(3));
1181
1182        std::thread::sleep(Duration::from_secs(2).add(Duration::from_millis(1)));
1183
1184        assert_eq!(map.len(), 4);
1185        assert_eq!(map.len_expired(), 2);
1186        assert_eq!(map.len_unchecked(), 6);
1187    }
1188
1189    #[test]
1190    fn std_update_expirable_entry() {
1191        let mut map = TimedMap::new();
1192
1193        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1194        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
1195
1196        std::thread::sleep(Duration::from_secs(2));
1197
1198        assert!(!map.expiries.contains_key(&1));
1199        assert!(map.expiries.contains_key(&5));
1200        assert_eq!(map.get(&1), Some(&"expirable value"));
1201    }
1202
1203    #[test]
1204    fn std_update_expirable_entry_status() {
1205        let mut map = TimedMap::new();
1206
1207        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1208        let old_status = map
1209            .update_expiration_status(1, Duration::from_secs(5))
1210            .expect("entry update shouldn't fail");
1211        assert!(matches!(old_status, Some(EntryStatus::ExpiresAtSeconds(_))));
1212
1213        std::thread::sleep(Duration::from_secs(3));
1214        assert!(!map.expiries.contains_key(&1));
1215        assert!(map.expiries.contains_key(&5));
1216        assert_eq!(map.get(&1), Some(&"expirable value"));
1217    }
1218
1219    #[test]
1220    fn std_update_constant_entry_status_returns_none() {
1221        let mut map = TimedMap::new();
1222
1223        map.insert_constant(1, "initial value");
1224
1225        let old_status = map
1226            .update_expiration_status(1, Duration::from_secs(5))
1227            .expect("entry update shouldn't fail");
1228
1229        assert!(old_status.is_none());
1230        assert_eq!(map.get(&1), Some(&"initial value"));
1231    }
1232
1233    #[test]
1234    fn std_update_expirable_entry_status_with_previou_time() {
1235        let mut map = TimedMap::new();
1236
1237        // Insert map entry followed by immediately updating expiration time
1238        map.insert_expirable(1, "expirable value", Duration::from_secs(5));
1239        map.update_expiration_status(1, Duration::from_secs(5))
1240            .expect("entry update shouldn't fail");
1241
1242        // We should still have our entry.
1243        assert_eq!(map.get(&1), Some(&"expirable value"));
1244        assert!(map.expiries.contains_key(&5));
1245    }
1246
1247    #[test]
1248    fn std_contains_key() {
1249        let mut map = TimedMap::new();
1250
1251        // Insert map entry and check if exists
1252        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1253        assert!(map.contains_key(&1));
1254    }
1255
1256    #[test]
1257    fn std_does_not_contain_key_anymore() {
1258        let mut map = TimedMap::new();
1259
1260        // Insert map entry and check if still exists after expiry
1261        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1262        std::thread::sleep(Duration::from_secs(2));
1263        assert!(!map.contains_key(&1));
1264    }
1265
1266    #[test]
1267    fn std_contains_key_unchecked() {
1268        let mut map = TimedMap::new();
1269
1270        map.insert_expirable(1, "expirable value", Duration::from_secs(1));
1271        std::thread::sleep(Duration::from_secs(2));
1272        assert!(map.contains_key_unchecked(&1));
1273    }
1274
1275    #[test]
1276    fn std_keys_and_is_empty_ignore_expired_entries() {
1277        let mut map = TimedMap::new();
1278
1279        map.insert_expirable(1, "expired value", Duration::from_secs(1));
1280        map.insert_constant(2, "constant value");
1281
1282        std::thread::sleep(Duration::from_secs(2));
1283
1284        assert_eq!(map.keys().as_slice(), &[2]);
1285        assert_eq!(map.keys_expired().as_slice(), &[1]);
1286        assert_eq!(map.keys_unchecked().as_slice(), &[1, 2]);
1287        assert!(!map.is_empty());
1288        assert!(!map.is_empty_unchecked());
1289
1290        map.remove(&2);
1291
1292        assert!(map.is_empty());
1293        assert_eq!(map.keys_expired().as_slice(), &[1]);
1294        assert!(!map.is_empty_unchecked());
1295    }
1296
1297    #[test]
1298    fn std_iter_empty() {
1299        let map: TimedMap<u64, &str> = TimedMap::new();
1300        assert_eq!(map.iter().count(), 0);
1301        assert_eq!(map.into_iter().count(), 0);
1302    }
1303
1304    #[test]
1305    fn std_iter_all_expired() {
1306        let mut map = TimedMap::new();
1307
1308        map.insert_expirable(1, "val1", Duration::from_secs(1));
1309        map.insert_expirable(2, "val2", Duration::from_secs(1));
1310
1311        std::thread::sleep(Duration::from_secs(2));
1312
1313        assert_eq!(map.iter().count(), 0);
1314        assert_eq!(map.into_iter().count(), 0);
1315    }
1316
1317    #[test]
1318    fn std_iter_mixed() {
1319        let mut map = TimedMap::new();
1320
1321        map.insert_expirable(1, "val1", Duration::from_secs(1));
1322        map.insert_constant(2, "val2");
1323        map.insert_expirable(3, "val3", Duration::from_secs(5));
1324
1325        // Only 1 should expire after this sleep
1326        std::thread::sleep(Duration::from_secs(2));
1327
1328        let mut collected: Vec<(&u64, &&str)> = map.iter().collect();
1329        collected.sort_by_key(|(k, _)| *k);
1330
1331        assert_eq!(collected.len(), 2);
1332        assert_eq!(collected[0], (&2, &"val2"));
1333        assert_eq!(collected[1], (&3, &"val3"));
1334    }
1335
1336    #[test]
1337    fn std_iter_mut_mixed() {
1338        let mut map = TimedMap::new();
1339
1340        map.insert_expirable(1, "val1", Duration::from_secs(1));
1341        map.insert_constant(2, "val2");
1342        map.insert_expirable(3, "val3", Duration::from_secs(5));
1343
1344        // Only 1 should expire after this sleep
1345        std::thread::sleep(Duration::from_secs(2));
1346
1347        for (k, v) in map.iter_mut() {
1348            if *k == 2 {
1349                *v = "modified_val2";
1350            }
1351        }
1352
1353        assert_eq!(map.get(&1), None);
1354        assert_eq!(map.get(&2), Some(&"modified_val2"));
1355        assert_eq!(map.get(&3), Some(&"val3"));
1356    }
1357}