use std::sync::atomic::{AtomicU64, Ordering};
use cached::macros::{cached, once};
use cached::stores::ExpiringCache;
use cached::time::{Duration, Instant};
use cached::{CachedExt, Expires, ExpiringLruCache};
static CALL_N: AtomicU64 = AtomicU64::new(0);
#[derive(Clone, Debug)]
struct MyValue {
data: String,
expires_at: Instant,
}
impl Expires for MyValue {
fn is_expired(&self) -> bool {
Instant::now() >= self.expires_at
}
}
#[cached(expires = true, key = "u64", convert = { user_id })]
fn fetch_token(user_id: u64, expiry_offset_ms: u64) -> MyValue {
println!(" -> [fetch_token] generating new token for user {user_id}...");
let n = CALL_N.fetch_add(1, Ordering::Relaxed);
MyValue {
data: format!("token-{user_id}-{n}"),
expires_at: Instant::now() + Duration::from_millis(expiry_offset_ms),
}
}
#[once(expires = true)]
fn get_session_token(expiry_offset_ms: u64) -> MyValue {
println!(" -> [get_session_token] generating new token...");
let n = CALL_N.fetch_add(1, Ordering::Relaxed);
MyValue {
data: format!("session-token-{n}"),
expires_at: Instant::now() + Duration::from_millis(expiry_offset_ms),
}
}
#[cached(expires = true, key = "u64", convert = { user_id })]
fn fetch_token_result(user_id: u64, expiry_offset_ms: u64, fail: bool) -> Result<MyValue, String> {
println!(" -> [fetch_token_result] generating token for user {user_id} (fail={fail})...");
if fail {
return Err(format!("upstream error fetching token for user {user_id}"));
}
let n = CALL_N.fetch_add(1, Ordering::Relaxed);
Ok(MyValue {
data: format!("token-{user_id}-{n}"),
expires_at: Instant::now() + Duration::from_millis(expiry_offset_ms),
})
}
#[cached(expires = true, key = "u64", convert = { user_id })]
fn fetch_token_option(user_id: u64, expiry_offset_ms: u64, found: bool) -> Option<MyValue> {
println!(" -> [fetch_token_option] generating token for user {user_id} (found={found})...");
if !found {
return None;
}
let n = CALL_N.fetch_add(1, Ordering::Relaxed);
Some(MyValue {
data: format!("token-{user_id}-{n}"),
expires_at: Instant::now() + Duration::from_millis(expiry_offset_ms),
})
}
fn main() {
let now = Instant::now();
println!("--- 1. ExpiringLruCache (Size-bounded) ---");
let mut lru_cache = ExpiringLruCache::builder().max_size(10).build().unwrap();
let quick_expiry = MyValue {
data: "Short-lived LRU response".to_string(),
expires_at: now + Duration::from_millis(500),
};
lru_cache.set("short", quick_expiry);
let long_expiry = MyValue {
data: "Long-lived LRU response".to_string(),
expires_at: now + Duration::from_secs(10),
};
lru_cache.set("long", long_expiry);
println!("Immediately after insertion into ExpiringLruCache:");
if let Some(val) = lru_cache.get(&"short") {
println!(" 'short' exists: '{}'", val.data);
}
if let Some(val) = lru_cache.get(&"long") {
println!(" 'long' exists: '{}'", val.data);
}
println!("\nWaiting 1 second...");
std::thread::sleep(Duration::from_secs(1));
println!("After waiting 1 second:");
match lru_cache.get(&"short") {
Some(val) => println!(" 'short' exists: '{}'", val.data),
None => println!(" 'short' has expired and was removed!"),
}
match lru_cache.get(&"long") {
Some(val) => println!(" 'long' exists: '{}' (still active)", val.data),
None => println!(" 'long' expired!"),
}
println!("\n--- 2. ExpiringCache (Size-unbounded) ---");
let mut expiring_cache = ExpiringCache::builder().build().unwrap();
let quick_expiry = MyValue {
data: "Short-lived response".to_string(),
expires_at: Instant::now() + Duration::from_millis(500),
};
expiring_cache.set("short", quick_expiry);
let long_expiry = MyValue {
data: "Long-lived response".to_string(),
expires_at: Instant::now() + Duration::from_secs(10),
};
expiring_cache.set("long", long_expiry);
println!("Immediately after insertion into ExpiringCache:");
if let Some(val) = expiring_cache.get(&"short") {
println!(" 'short' exists: '{}'", val.data);
}
if let Some(val) = expiring_cache.get(&"long") {
println!(" 'long' exists: '{}'", val.data);
}
println!("\nWaiting 1 second...");
std::thread::sleep(Duration::from_secs(1));
println!("After waiting 1 second:");
match expiring_cache.get(&"short") {
Some(val) => println!(" 'short' exists: '{}'", val.data),
None => println!(" 'short' has expired and was removed!"),
}
match expiring_cache.get(&"long") {
Some(val) => println!(" 'long' exists: '{}' (still active)", val.data),
None => println!(" 'long' expired!"),
}
println!("\n--- 3. #[cached(expires = true)] Macro (keyed) ---");
println!("First call for user 1 (expires in 500ms):");
let u1_t1 = fetch_token(1, 500);
println!(" Returned: '{}'", u1_t1.data);
println!("First call for user 2 (expires in 10s):");
let u2_t1 = fetch_token(2, 10_000);
println!(" Returned: '{}'", u2_t1.data);
println!("\nSecond call for user 1 (cache hit):");
let u1_t2 = fetch_token(1, 500);
println!(" Returned: '{}' (same token)", u1_t2.data);
println!("\nWaiting 1 second...");
std::thread::sleep(Duration::from_secs(1));
println!("\nAfter 1 second:");
let u1_t3 = fetch_token(1, 500);
println!(" user 1: '{}' (re-evaluated — was expired)", u1_t3.data);
let u2_t2 = fetch_token(2, 10_000);
println!(" user 2: '{}' (cache hit — still live)", u2_t2.data);
println!("\n--- 4. #[once(expires = true)] Macro (single value) ---");
println!("First call (creating token that expires in 500ms):");
let t1 = get_session_token(500);
println!(" Returned: '{}'", t1.data);
println!("\nSecond call (immediately after):");
let t2 = get_session_token(500);
println!(" Returned: '{}' (cache hit)", t2.data);
println!("\nWaiting 1 second...");
std::thread::sleep(Duration::from_secs(1));
println!("Third call (after token has expired):");
let t3 = get_session_token(500);
println!(" Returned: '{}' (cache miss, re-evaluated)", t3.data);
println!("\n--- 5. #[cached(expires = true)] returning Result ---");
println!("First call for user 1 (fails):");
assert!(fetch_token_result(1, 500, true).is_err());
println!("Second call for user 1 (still fails, Err was not cached):");
assert!(fetch_token_result(1, 500, true).is_err());
println!("\nThird call for user 1 (succeeds, Ok is cached):");
let r1 = fetch_token_result(1, 500, false).unwrap();
println!(" Returned: '{}'", r1.data);
println!("Fourth call for user 1 (cache hit, function not run):");
let r2 = fetch_token_result(1, 500, true).unwrap();
println!(" Returned: '{}' (same token)", r2.data);
println!("\nWaiting 1 second...");
std::thread::sleep(Duration::from_secs(1));
println!("Fifth call for user 1 (cached Ok expired, re-evaluated):");
let r3 = fetch_token_result(1, 500, false).unwrap();
println!(" Returned: '{}'", r3.data);
println!("\n--- 6. #[cached(expires = true)] returning Option ---");
println!("First call for user 2 (None):");
assert!(fetch_token_option(2, 500, false).is_none());
println!("Second call for user 2 (still None, None was not cached):");
assert!(fetch_token_option(2, 500, false).is_none());
println!("\nThird call for user 2 (Some, cached):");
let o1 = fetch_token_option(2, 500, true).unwrap();
println!(" Returned: '{}'", o1.data);
println!("Fourth call for user 2 (cache hit, function not run):");
let o2 = fetch_token_option(2, 500, false).unwrap();
println!(" Returned: '{}' (same token)", o2.data);
println!("\nWaiting 1 second...");
std::thread::sleep(Duration::from_secs(1));
println!("Fifth call for user 2 (cached Some expired, re-evaluated):");
let o3 = fetch_token_option(2, 500, true).unwrap();
println!(" Returned: '{}'", o3.data);
}