#![warn(clippy::all, clippy::pedantic, clippy::cargo, clippy::nursery)]
#![allow(clippy::must_use_candidate)]
use std::{
borrow::Borrow,
collections::HashMap,
hash::Hash,
ops::{Deref, DerefMut},
time::{Duration, Instant},
};
#[cfg(test)]
mod test;
type ExpiringMapInner<K, V> = HashMap<K, ExpiryValue<V>>;
#[derive(Debug, Clone)]
pub struct ExpiryValue<T> {
inserted: Instant,
ttl: Duration,
value: T,
}
impl<T> Deref for ExpiryValue<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T> ExpiryValue<T> {
pub const fn inserted(&self) -> Instant {
self.inserted
}
pub const fn ttl(&self) -> Duration {
self.ttl
}
pub fn remaining(&self) -> Duration {
self.ttl.saturating_sub(self.inserted.elapsed())
}
pub fn value(self) -> T {
self.value
}
pub fn expired(&self) -> bool {
self.remaining().is_zero()
}
pub fn not_expired(&self) -> bool {
!self.expired()
}
}
#[derive(Debug)]
pub struct ExpiringMap<K, V> {
last_size: usize,
inner: ExpiringMapInner<K, V>,
}
#[derive(Debug)]
pub struct ExpiringSet<K>(ExpiringMap<K, ()>);
impl<K> Deref for ExpiringSet<K> {
type Target = ExpiringMap<K, ()>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<K> DerefMut for ExpiringSet<K> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<K: PartialEq + Eq + Hash, V> ExpiringMap<K, V> {
const MINIMUM_VACUUM_SIZE: usize = 8;
pub fn new() -> Self {
Self::with_capacity(0)
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
inner: ExpiringMapInner::with_capacity(capacity),
last_size: Self::MINIMUM_VACUUM_SIZE,
}
}
pub fn vacuum(&mut self) {
let now = Instant::now();
self.inner
.retain(|_, expiry| now.duration_since(expiry.inserted) < expiry.ttl);
if self.inner.len() > Self::MINIMUM_VACUUM_SIZE {
self.last_size = self.inner.len();
} else {
self.last_size = Self::MINIMUM_VACUUM_SIZE;
}
}
pub fn vacuum_if_needed(&mut self) {
if (self.last_size * 3) / 2 < self.inner.len() {
self.vacuum();
}
}
pub fn get_meta<Q>(&self, key: &Q) -> Option<&ExpiryValue<V>>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.inner.get(key).filter(|x| x.not_expired())
}
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.get_meta(key).map(|v| &v.value)
}
pub fn get_key_value<Q>(&self, key: &Q) -> Option<(&K, &V)>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.inner
.get_key_value(key)
.filter(|(_, v)| v.not_expired())
.map(|(k, v)| (k, &v.value))
}
pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.inner
.get_mut(key)
.filter(|x| x.not_expired())
.map(|v| &mut v.value)
}
pub fn insert(&mut self, key: K, value: V, ttl: Duration) -> Option<ExpiryValue<V>> {
self.vacuum_if_needed();
let entry = ExpiryValue {
inserted: Instant::now(),
ttl,
value,
};
self.inner
.insert(key, entry)
.filter(ExpiryValue::not_expired)
}
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.get_meta(key).is_some_and(ExpiryValue::not_expired)
}
pub fn remove<Q>(&mut self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.inner
.remove(key)
.as_ref()
.is_some_and(ExpiryValue::not_expired)
}
pub const fn last_size(&self) -> usize {
self.last_size
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.len() == 0
}
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
pub fn reserve(&mut self, addtional: usize) {
self.inner.reserve(addtional);
}
pub fn shrink_to_fit(&mut self) {
self.vacuum();
self.inner.shrink_to_fit();
}
pub fn shrink_to(&mut self, min_capacity: usize) {
self.vacuum();
self.inner.shrink_to(min_capacity);
}
pub fn remove_entry<Q>(&mut self, key: &Q) -> Option<(K, V)>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.inner
.remove_entry(key)
.filter(|(_, v)| v.not_expired())
.map(|(k, v)| (k, v.value))
}
}
impl<K: PartialEq + Eq + Hash> ExpiringSet<K> {
pub fn new() -> Self {
Self::with_capacity(0)
}
pub fn with_capacity(capacity: usize) -> Self {
Self(ExpiringMap::with_capacity(capacity))
}
pub fn insert(&mut self, key: K, ttl: Duration) -> bool {
self.vacuum_if_needed();
let entry = ExpiryValue {
inserted: Instant::now(),
ttl,
value: (),
};
self.inner
.insert(key, entry)
.filter(ExpiryValue::not_expired)
.is_some()
}
pub fn contains<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.0.contains_key(key)
}
pub fn take<Q>(&mut self, key: &Q) -> Option<K>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.0.remove_entry(key).map(|(k, ())| k)
}
pub fn shrink_to_fit(&mut self) {
self.0.shrink_to_fit();
}
pub fn shrink_to(&mut self, min_capacity: usize) {
self.0.shrink_to(min_capacity);
}
}
impl<K: PartialEq + Eq + Hash, V> Default for ExpiringMap<K, V> {
fn default() -> Self {
Self::new()
}
}
impl<K: PartialEq + Eq + Hash> Default for ExpiringSet<K> {
fn default() -> Self {
Self::new()
}
}