#lru-cache #ttl-cache #cache #memory-cache #ttl #lru

philiprehberger-cache-kit

Generic LRU cache with TTL, tags, and async support for Rust

15 releases (5 breaking)

new 0.6.0 Apr 7, 2026
0.5.0 Apr 6, 2026
0.4.4 Apr 1, 2026
0.4.3 Mar 27, 2026
0.1.0 Mar 10, 2026

#109 in Caching

MIT license

39KB
815 lines

rs-cache-kit

CI Crates.io Last updated

Generic LRU cache with TTL, tags, and async support for Rust

Installation

[dependencies]
philiprehberger-cache-kit = "0.6.0"

Usage

use philiprehberger_cache_kit::Cache;
use std::time::Duration;

let cache = Cache::new(1000, Some(Duration::from_secs(300)));

cache.set("key".to_string(), "value".to_string());
let val = cache.get(&"key".to_string()); // Some("value")

Custom TTL and Tags

cache.set_with(
    "user:1".to_string(),
    user_data,
    Some(Duration::from_secs(60)),
    &["users", "team-a"],
);

Tag-Based Invalidation

let removed = cache.invalidate_by_tag("team-a");
println!("Removed {} entries", removed);

Thread Safety

The cache is Clone and uses Arc<RwLock<...>> internally — safe to share across threads.

let cache = Cache::new(100, None);
let cache2 = cache.clone();

std::thread::spawn(move || {
    cache2.set("key".to_string(), "from thread".to_string());
});

Other Operations

cache.has(&key)      // check existence
cache.delete(&key)   // delete entry
cache.size()         // entry count
cache.clear()        // remove all

Get or Insert

let value = cache.get_or_insert_with("key".to_string(), || {
    expensive_computation()
});

Cache Stats

Track hit/miss/eviction counters for monitoring and tuning:

let cache = Cache::new(100, None);
cache.set("a".to_string(), 1);
cache.get(&"a".to_string()); // hit
cache.get(&"z".to_string()); // miss

let stats = cache.stats();
println!("Hits: {}, Misses: {}, Evictions: {}", stats.hits, stats.misses, stats.evictions);

Batch Get

Retrieve multiple keys in one call:

cache.set("x".to_string(), 1);
cache.set("y".to_string(), 2);

let results = cache.get_many(&["x".to_string(), "y".to_string(), "z".to_string()]);
// returns HashMap with "x" => 1, "y" => 2 (missing keys omitted)

Conditional Delete

Remove entries matching a predicate:

cache.set("small".to_string(), 1);
cache.set("big".to_string(), 1000);

let removed = cache.delete_where(|_key, value| *value > 100);
// removed == 1, "big" is gone

Peek Without LRU Update

use philiprehberger_cache_kit::Cache;

let cache = Cache::new(100, None);
cache.set("key", "value");

// Read without affecting eviction order
let val = cache.peek(&"key");

Eviction Callback

use philiprehberger_cache_kit::Cache;
use std::sync::{Arc, Mutex};

let cache = Cache::new(2, None);
let log = Arc::new(Mutex::new(Vec::new()));
let log2 = log.clone();
cache.on_evict(move |key: &String, _val: &String| {
    log2.lock().unwrap().push(key.clone());
});

cache.set("a".into(), "1".into());
cache.set("b".into(), "2".into());
cache.set("c".into(), "3".into()); // evicts "a"

TTL Remaining

use philiprehberger_cache_kit::Cache;
use std::time::Duration;

let cache = Cache::new(100, Some(Duration::from_secs(60)));
cache.set("key", "value");

if let Some(remaining) = cache.entry_ttl_remaining(&"key") {
    println!("TTL remaining: {:?}", remaining);
}

Maintenance

cache.len()             // entry count (alias for size)
cache.is_empty()        // check if empty
cache.max_size()        // max capacity
cache.keys()            // all non-expired keys
cache.iter_live()       // all non-expired (key, value) pairs
cache.remove_expired()  // clean up expired entries
cache.purge_expired()   // clean up expired entries (alias)

Feature Flags

This crate exposes an optional serde feature that derives Serialize and Deserialize on CacheStats for integration with JSON and other serde-based formats.

[dependencies]
philiprehberger-cache-kit = { version = "0.6.0", features = ["serde"] }

API

Function / Type Description
Cache::new(max_size, default_ttl) Create a new cache with max capacity and optional default TTL
Cache::default() Create a cache with max_size=100 and no TTL
cache.set(key, value) Insert a value with default TTL and no tags
cache.set_with(key, value, ttl, tags) Insert a value with custom TTL and tags
cache.get(key) Get a value (returns None if missing or expired)
cache.get_many(keys) Retrieve multiple values at once
cache.get_or_insert_with(key, f) Get or compute and insert a value
cache.has(key) Check if a key exists and is not expired
cache.delete(key) Delete an entry by key
cache.delete_where(predicate) Remove entries matching a predicate
cache.invalidate_by_tag(tag) Remove all entries with the given tag
cache.clear() Remove all entries
cache.size() / cache.len() Return the number of entries
cache.is_empty() Check if the cache is empty
cache.max_size() Return the max capacity
cache.keys() Return all non-expired keys
cache.iter_live() Return all non-expired (key, value) pairs as a Vec
cache.remove_expired() Clean up expired entries
cache.purge_expired() Clean up expired entries (public wrapper around remove_expired)
cache.peek(key) Read a value without updating LRU order
cache.on_evict(callback) Register a callback for cache evictions
cache.entry_ttl_remaining(key) Check remaining TTL for an entry
cache.stats() Return hit/miss/eviction counters as CacheStats
CacheStats Struct with hits, misses, evictions fields

Development

cargo test
cargo clippy -- -D warnings

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT

Dependencies

~145KB