Loading page…
Rust walkthroughs
Loading page…
The lru crate provides a fast, thread-safe LRU (Least Recently Used) cache implementation in Rust. An LRU cache automatically evicts the least recently accessed items when it reaches capacity, making it ideal for caching frequently accessed data while bounding memory usage. The crate offers both a standard LruCache and a thread-safe LruCache with Arc and Mutex for concurrent access.
Key concepts:
# Cargo.toml
[dependencies]
lru = "0.12"use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
// Create an LRU cache with capacity for 3 items
let mut cache: LruCache<&str, i32> =
LruCache::new(NonZeroUsize::new(3).unwrap());
// Insert items
cache.put("a", 1);
cache.put("b", 2);
cache.put("c", 3);
// Access "a" - moves it to most recent
let _ = cache.get(&"a");
// Insert "d" - evicts "b" (least recently used)
cache.put("d", 4);
println!("Cache contains: {:?}", cache.iter().collect::<Vec<_>>());
// Shows: [("a", 1), ("c", 3), ("d", 4)]
assert!(cache.contains(&"a"));
assert!(!cache.contains(&"b")); // evicted
}use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
// Create cache with capacity for 5 items
let mut cache: LruCache<String, i32> =
LruCache::new(NonZeroUsize::new(5).unwrap());
// Insert items
cache.put("one".to_string(), 1);
cache.put("two".to_string(), 2);
cache.put("three".to_string(), 3);
// Get item (updates access order)
if let Some(value) = cache.get(&"one".to_string()) {
println!("Got: {}", value);
}
// Check if contains
println!("Contains 'two': {}", cache.contains(&"two".to_string()));
// Remove item
if let Some(value) = cache.pop(&"two".to_string()) {
println!("Removed: {}", value);
}
// Get current size
println!("Cache size: {}", cache.len());
println!("Cache capacity: {}", cache.cap());
}use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
let mut cache: LruCache<i32, &str> =
LruCache::new(NonZeroUsize::new(3).unwrap());
// Fill cache to capacity
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
println!("After filling: {:?}",
cache.iter().map(|(k, v)| (*k, *v)).collect::<Vec<_>>());
// Add another item - evicts least recently used (key 1)
cache.put(4, "four");
println!("After adding 4: {:?}",
cache.iter().map(|(k, v)| (*k, *v)).collect::<Vec<_>>());
// Order: most recent first
// Access key 2 to make it more recent
let _ = cache.get(&2);
println!("After accessing 2: {:?}",
cache.iter().map(|(k, v)| (*k, *v)).collect::<Vec<_>>());
// Add another item - evicts key 3 (now least recent)
cache.put(5, "five");
println!("After adding 5: {:?}",
cache.iter().map(|(k, v)| (*k, *v)).collect::<Vec<_>>());
// Check what's in cache
assert!(cache.contains(&2));
assert!(cache.contains(&4));
assert!(cache.contains(&5));
assert!(!cache.contains(&1)); // evicted
assert!(!cache.contains(&3)); // evicted
}use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
let mut cache: LruCache<&str, i32> =
LruCache::new(NonZeroUsize::new(3).unwrap());
// put returns None for new keys
let old = cache.put("a", 1);
println!("Put 'a': {:?}", old); // None
// put returns old value for existing keys
let old = cache.put("a", 10);
println!("Put 'a' again: {:?}", old); // Some(1)
// put returns evicted value when capacity exceeded
cache.put("b", 2);
cache.put("c", 3);
let evicted = cache.put("d", 4);
println!("Evicted when adding 'd': {:?}", evicted);
// Some(("a", 1)) - "a" was least recently used
}use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
let mut cache: LruCache<&str, i32> =
LruCache::new(NonZeroUsize::new(3).unwrap());
cache.put("a", 1);
cache.put("b", 2);
cache.put("c", 3);
// Order: a (LRU) -> b -> c (MRU)
println!("Initial order: {:?}",
cache.iter().map(|(k, _)| *k).collect::<Vec<_>>());
// get() updates access order
let _ = cache.get(&"a");
println!("After get('a'): {:?}",
cache.iter().map(|(k, _)| *k).collect::<Vec<_>>());
// Now: b (LRU) -> c -> a (MRU)
// peek() does NOT update access order
let _ = cache.peek(&"b");
println!("After peek('b'): {:?}",
cache.iter().map(|(k, _)| *k).collect::<Vec<_>>());
// Order unchanged: b (LRU) -> c -> a (MRU)
// peek_mut() also doesn't update order
if let Some(value) = cache.peek_mut(&"b") {
*value *= 10;
}
println!("After peek_mut('b'): {:?}",
cache.iter().map(|(k, v)| (*k, *v)).collect::<Vec<_>>());
}use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
let mut cache: LruCache<&str, i32> =
LruCache::new(NonZeroUsize::new(3).unwrap());
// or_insert() - insert if missing
cache.entry("a").or_insert(1);
cache.entry("a").or_insert(100); // Already exists, no change
println!("a = {:?}", cache.get(&"a"));
// or_insert_with() - lazy insertion
cache.entry("b").or_insert_with(|| {
println!("Computing value for 'b'");
2
});
cache.entry("b").or_insert_with(|| {
println!("This won't print");
200
});
// or_default() - insert default value
cache.entry("c").or_default();
println!("c = {:?}", cache.get(&"c"));
// and_modify() - modify existing value
cache.entry("a").and_modify(|v| *v += 1);
println!("a after modify = {:?}", cache.get(&"a"));
// Chain operations
cache.entry("d")
.and_modify(|v| *v += 1)
.or_insert(4);
println!("d = {:?}", cache.get(&"d"));
}use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
let mut cache: LruCache<i32, &str> =
LruCache::new(NonZeroUsize::new(5).unwrap());
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
cache.put(4, "four");
cache.put(5, "five");
// Iterate from most to least recently used
println!("MRU to LRU:");
for (key, value) in cache.iter() {
println!(" {} -> {}", key, value);
}
// Iterate from least to most recently used
println!("\nLRU to MRU:");
for (key, value) in cache.iter().rev() {
println!(" {} -> {}", key, value);
}
// Mutable iteration
println!("\nAfter modification:");
for (key, value) in cache.iter_mut() {
*value = value.to_uppercase();
}
for (key, value) in cache.iter() {
println!(" {} -> {}", key, value);
}
// Keys only
println!("\nKeys only:");
for key in cache.keys() {
println!(" {}", key);
}
// Values only
println!("\nValues only:");
for value in cache.values() {
println!(" {}", value);
}
}use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
let mut cache: LruCache<i32, &str> =
LruCache::new(NonZeroUsize::new(5).unwrap());
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
cache.put(4, "four");
cache.put(5, "five");
println!("Size: {}, Capacity: {}", cache.len(), cache.cap());
// Resize to smaller capacity - evicts LRU items
cache.resize(NonZeroUsize::new(3).unwrap());
println!("\nAfter resize to 3:");
println!("Size: {}, Capacity: {}", cache.len(), cache.cap());
println!("Remaining: {:?}",
cache.iter().map(|(k, _)| *k).collect::<Vec<_>>());
// Resize to larger capacity
cache.resize(NonZeroUsize::new(10).unwrap());
println!("\nAfter resize to 10:");
println!("Size: {}, Capacity: {}", cache.len(), cache.cap());
// Add more items
cache.put(6, "six");
cache.put(7, "seven");
println!("After adding more: {:?}",
cache.iter().map(|(k, _)| *k).collect::<Vec<_>>());
}use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
let mut cache: LruCache<&str, i32> =
LruCache::new(NonZeroUsize::new(5).unwrap());
cache.put("a", 1);
cache.put("b", 2);
cache.put("c", 3);
println!("Before clear: {} items", cache.len());
// Clear all items
cache.clear();
println!("After clear: {} items", cache.len());
assert!(cache.is_empty());
// Cache still has its capacity
println!("Capacity: {}", cache.cap());
}use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
let mut cache: LruCache<&str, i32> =
LruCache::new(NonZeroUsize::new(3).unwrap());
// push() returns evicted item if cache is full
let evicted = cache.push("a", 1);
println!("Push 'a': evicted {:?}", evicted);
cache.push("b", 2);
cache.push("c", 3);
// Now cache is full
let evicted = cache.push("d", 4);
println!("Push 'd': evicted {:?}", evicted);
// Some(("a", 1))
// If key exists, push updates value but doesn't evict
let evicted = cache.push("b", 20);
println!("Push 'b' again: evicted {:?}", evicted);
// None - no eviction since 'b' was already in cache
println!("Cache contents: {:?}",
cache.iter().map(|(k, v)| (*k, *v)).collect::<Vec<_>>());
}use lru::LruCache;
use std::num::NonZeroUsize;
fn main() {
let mut cache: LruCache<&str, i32> =
LruCache::new(NonZeroUsize::new(3).unwrap());
cache.put("a", 1);
cache.put("b", 2);
// Check existence without updating order
println!("Contains 'a': {}", cache.contains(&"a"));
println!("Contains 'c': {}", cache.contains(&"c"));
// Get mutable reference (updates order)
if let Some(value) = cache.get_mut(&"a") {
*value += 10;
println!("Modified 'a' to {}", value);
}
// Verify modification
println!("'a' is now: {:?}", cache.get(&"a"));
}use lru::LruCache;
use std::num::NonZeroUsize;
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Wrap LruCache in Arc<Mutex<>> for thread safety
let cache = Arc::new(Mutex::new(
LruCache::new(NonZeroUsize::new(10).unwrap())
));
let mut handles = vec![];
for i in 0..5 {
let cache_clone = Arc::clone(&cache);
let handle = thread::spawn(move || {
let mut cache = cache_clone.lock().unwrap();
cache.put(format!("key-{}", i), i * 10);
println!("Thread {} inserted key-{}", i, i);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
// Check cache contents
let cache = cache.lock().unwrap();
println!("\nCache contents:");
for (key, value) in cache.iter() {
println!(" {} -> {}", key, value);
}
println!("Size: {}", cache.len());
}use lru::LruCache;
use std::num::NonZeroUsize;
// Expensive computation
fn expensive_computation(n: i32) -> i32 {
println!("Computing fib({})...", n);
if n <= 1 { n } else { expensive_computation(n - 1) + expensive_computation(n - 2) }
}
struct CachedCalculator {
cache: LruCache<i32, i32>,
hits: u64,
misses: u64,
}
impl CachedCalculator {
fn new(capacity: usize) -> Self {
Self {
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
hits: 0,
misses: 0,
}
}
fn compute(&mut self, n: i32) -> i32 {
if let Some(&result) = self.cache.get(&n) {
self.hits += 1;
println!("Cache hit for fib({})", n);
return result;
}
self.misses += 1;
let result = expensive_computation(n);
self.cache.put(n, result);
result
}
fn stats(&self) -> (u64, u64) {
(self.hits, self.misses)
}
}
fn main() {
let mut calc = CachedCalculator::new(10);
// First call computes
let r1 = calc.compute(10);
println!("Result: {}", r1);
// Second call uses cache
let r2 = calc.compute(10);
println!("Result: {}", r2);
// Cache miss
let r3 = calc.compute(15);
println!("Result: {}", r3);
let (hits, misses) = calc.stats();
println!("\nCache stats: {} hits, {} misses", hits, misses);
}use lru::LruCache;
use std::num::NonZeroUsize;
use std::collections::HashMap;
struct WebCache {
cache: LruCache<String, String>,
requests: u64,
cache_hits: u64,
}
impl WebCache {
fn new(capacity: usize) -> Self {
Self {
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
requests: 0,
cache_hits: 0,
}
}
fn get(&mut self, url: &str) -> Option<&String> {
self.requests += 1;
if self.cache.contains(&url.to_string()) {
self.cache_hits += 1;
}
self.cache.get(&url.to_string())
}
fn put(&mut self, url: String, content: String) {
self.cache.put(url, content);
}
fn stats(&self) -> (u64, u64, f64) {
let hit_rate = if self.requests > 0 {
self.cache_hits as f64 / self.requests as f64 * 100.0
} else {
0.0
};
(self.requests, self.cache_hits, hit_rate)
}
}
fn main() {
let mut cache = WebCache::new(3);
// Simulate requests
cache.put("https://example.com".to_string(), "Example content".to_string());
cache.put("https://rust-lang.org".to_string(), "Rust homepage".to_string());
cache.put("https://docs.rs".to_string(), "Docs.rs".to_string());
// Cache hit
if let Some(content) = cache.get("https://example.com") {
println!("Got: {}", content);
}
// Cache hit
if let Some(content) = cache.get("https://rust-lang.org") {
println!("Got: {}", content);
}
// Add new URL - will evict least recently used
cache.put("https://crates.io".to_string(), "Crates.io".to_string());
// Cache miss (was evicted)
if cache.get("https://docs.rs").is_none() {
println!("docs.rs was evicted");
}
let (requests, hits, rate) = cache.stats();
println!("\nStats: {} requests, {} hits, {:.1}% hit rate",
requests, hits, rate);
}use lru::LruCache;
use std::num::NonZeroUsize;
struct QueryCache {
cache: LruCache<String, Vec<String>>,
}
impl QueryCache {
fn new(capacity: usize) -> Self {
Self {
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
}
}
fn query(&mut self, sql: &str) -> Vec<String> {
if let Some(results) = self.cache.get(&sql.to_string()) {
println!("Cache hit for: {}", sql);
return results.clone();
}
println!("Executing query: {}", sql);
let results = self.execute_query(sql);
self.cache.put(sql.to_string(), results.clone());
results
}
fn execute_query(&self, sql: &str) -> Vec<String> {
// Simulated database query
if sql.contains("users") {
vec!["alice".to_string(), "bob".to_string(), "charlie".to_string()]
} else if sql.contains("products") {
vec!["widget".to_string(), "gadget".to_string()]
} else {
vec![]
}
}
}
fn main() {
let mut cache = QueryCache::new(5);
// First query - executes
let users = cache.query("SELECT * FROM users");
println!("Users: {:?}", users);
// Same query - cached
let users_again = cache.query("SELECT * FROM users");
println!("Users again: {:?}", users_again);
// Different query
let products = cache.query("SELECT * FROM products");
println!("Products: {:?}", products);
}use lru::LruCache;
use std::num::NonZeroUsize;
use std::time::{Duration, Instant};
struct TimedEntry<T> {
value: T,
expires_at: Instant,
}
struct TimedLruCache<K, V> {
cache: LruCache<K, TimedEntry<V>>,
default_ttl: Duration,
}
impl<K: std::hash::Hash + Eq + Clone, V: Clone> TimedLruCache<K, V> {
fn new(capacity: usize, default_ttl: Duration) -> Self {
Self {
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
default_ttl,
}
}
fn put(&mut self, key: K, value: V) {
let entry = TimedEntry {
value,
expires_at: Instant::now() + self.default_ttl,
};
self.cache.put(key, entry);
}
fn get(&mut self, key: &K) -> Option<V> {
if let Some(entry) = self.cache.get(key) {
if Instant::now() < entry.expires_at {
return Some(entry.value.clone());
} else {
// Expired - remove and return None
self.cache.pop(key);
}
}
None
}
fn remove_expired(&mut self) -> usize {
let now = Instant::now();
let expired: Vec<K> = self.cache
.iter()
.filter(|(_, entry)| entry.expires_at <= now)
.map(|(k, _)| k.clone())
.collect();
let count = expired.len();
for key in expired {
self.cache.pop(&key);
}
count
}
}
fn main() {
let mut cache = TimedLruCache::new(10, Duration::from_millis(100));
cache.put("a", 1);
cache.put("b", 2);
// Immediately available
println!("a: {:?}", cache.get(&"a".to_string()));
println!("b: {:?}", cache.get(&"b".to_string()));
// Wait for expiration
std::thread::sleep(Duration::from_millis(150));
// Now expired
println!("After sleep - a: {:?}", cache.get(&"a".to_string()));
println!("After sleep - b: {:?}", cache.get(&"b".to_string()));
}use lru::LruCache;
use std::num::NonZeroUsize;
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
struct CacheKey {
region: String,
resource_id: i32,
}
impl CacheKey {
fn new(region: &str, id: i32) -> Self {
Self {
region: region.to_string(),
resource_id: id,
}
}
}
#[derive(Debug, Clone)]
struct Resource {
name: String,
data: String,
}
fn main() {
let mut cache: LruCache<CacheKey, Resource> =
LruCache::new(NonZeroUsize::new(5).unwrap());
// Insert with custom key
cache.put(
CacheKey::new("us-east-1", 100),
Resource { name: "Server A".to_string(), data: "data".to_string() }
);
cache.put(
CacheKey::new("eu-west-1", 200),
Resource { name: "Server B".to_string(), data: "data".to_string() }
);
// Retrieve with custom key
let key = CacheKey::new("us-east-1", 100);
if let Some(resource) = cache.get(&key) {
println!("Found: {:?}", resource);
}
// Check existence
let other_key = CacheKey::new("ap-south-1", 300);
println!("Contains {:?}: {}", other_key, cache.contains(&other_key));
}use lru::LruCache;
use std::num::NonZeroUsize;
use std::sync::{Arc, Mutex};
use std::thread;
#[derive(Debug, Clone)]
struct HttpResponse {
status: u16,
body: String,
headers: Vec<(String, String)>,
}
struct HttpCache {
cache: LruCache<String, HttpResponse>,
}
impl HttpCache {
fn new(capacity: usize) -> Self {
Self {
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
}
}
fn get(&mut self, url: &str) -> Option<&HttpResponse> {
self.cache.get(&url.to_string())
}
fn put(&mut self, url: String, response: HttpResponse) -> Option<HttpResponse> {
self.cache.put(url, response)
}
fn get_or_fetch(&mut self, url: &str) -> HttpResponse {
if let Some(response) = self.cache.get(&url.to_string()) {
println!("Cache hit: {}", url);
return response.clone();
}
println!("Fetching: {}", url);
// Simulate HTTP request
let response = HttpResponse {
status: 200,
body: format!("Content for {}", url),
headers: vec![
("content-type".to_string(), "text/html".to_string()),
],
};
self.cache.put(url.to_string(), response.clone());
response
}
}
fn main() {
let cache = Arc::new(Mutex::new(HttpCache::new(3)));
// Simulate multiple requests
let urls = vec![
"https://example.com",
"https://rust-lang.org",
"https://example.com", // Should hit cache
"https://docs.rs",
"https://rust-lang.org", // Should hit cache
];
for url in urls {
let mut cache = cache.lock().unwrap();
let response = cache.get_or_fetch(url);
println!(" Response status: {}\n", response.status);
}
}use lru::LruCache;
use std::num::NonZeroUsize;
use std::collections::HashMap;
struct ConfigCache {
cache: LruCache<String, HashMap<String, String>>,
}
impl ConfigCache {
fn new(capacity: usize) -> Self {
Self {
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
}
}
fn get_config(&mut self, service: &str) -> HashMap<String, String> {
if let Some(config) = self.cache.get(&service.to_string()) {
println!("Config cache hit for: {}", service);
return config.clone();
}
println!("Loading config for: {}", service);
let config = self.load_config(service);
self.cache.put(service.to_string(), config.clone());
config
}
fn load_config(&self, service: &str) -> HashMap<String, String> {
// Simulate config loading
let mut config = HashMap::new();
config.insert("timeout".to_string(), "30".to_string());
config.insert("retries".to_string(), "3".to_string());
config.insert("service".to_string(), service.to_string());
config
}
fn invalidate(&mut self, service: &str) {
self.cache.pop(&service.to_string());
println!("Invalidated config for: {}", service);
}
}
fn main() {
let mut cache = ConfigCache::new(5);
// Load configs
let api_config = cache.get_config("api-service");
println!("API config: {:?}", api_config);
let db_config = cache.get_config("db-service");
println!("DB config: {:?}", db_config);
// Cache hit
let api_config_again = cache.get_config("api-service");
println!("API config again: {:?}", api_config_again);
// Invalidate
cache.invalidate("api-service");
// Cache miss after invalidation
let api_config_reload = cache.get_config("api-service");
println!("API config reloaded: {:?}", api_config_reload);
}use lru::LruCache;
use std::num::NonZeroUsize;
struct Memoizer<F, K, V>
where
F: Fn(K) -> V,
K: std::hash::Hash + Eq,
{
cache: LruCache<K, V>,
func: F,
}
impl<F, K, V> Memoizer<F, K, V>
where
F: Fn(K) -> V,
K: std::hash::Hash + Eq + Clone,
V: Clone,
{
fn new(capacity: usize, func: F) -> Self {
Self {
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
func,
}
}
fn call(&mut self, arg: K) -> V {
if let Some(value) = self.cache.get(&arg) {
return value.clone();
}
let value = (self.func)(arg.clone());
self.cache.put(arg, value.clone());
value
}
}
fn main() {
// Memoize expensive string operations
let mut uppercaser = Memoizer::new(10, |s: String| {
println!("Computing uppercase for: {}", s);
s.to_uppercase()
});
println!("Result: {}", uppercaser.call("hello".to_string()));
println!("Result: {}", uppercaser.call("hello".to_string())); // Cached
println!("Result: {}", uppercaser.call("world".to_string()));
println!("Result: {}", uppercaser.call("hello".to_string())); // Still cached
// Memoize factorial
let mut factorial = Memoizer::new(20, |n: i32| {
println!("Computing factorial({})", n);
(1..=n).product::<i32>()
});
println!("5! = {}", factorial.call(5));
println!("5! = {}", factorial.call(5)); // Cached
println!("10! = {}", factorial.call(10));
}use lru::LruCache;
use std::num::NonZeroUsize;
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
#[derive(Debug, Clone)]
struct Session {
user_id: String,
created_at: u64,
data: HashMap<String, String>,
}
struct SessionCache {
cache: LruCache<String, Session>,
}
impl SessionCache {
fn new(capacity: usize) -> Self {
Self {
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
}
}
fn create_session(&mut self, session_id: String, user_id: String) -> Session {
let session = Session {
user_id,
created_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
data: HashMap::new(),
};
self.cache.put(session_id.clone(), session.clone());
println!("Created session {} for user {}", session_id, session.user_id);
session
}
fn get_session(&mut self, session_id: &str) -> Option<&Session> {
self.cache.get(&session_id.to_string())
}
fn update_session(&mut self, session_id: &str, key: String, value: String) -> bool {
if let Some(session) = self.cache.get_mut(&session_id.to_string()) {
session.data.insert(key, value);
true
} else {
false
}
}
fn delete_session(&mut self, session_id: &str) -> Option<Session> {
self.cache.pop(&session_id.to_string())
}
}
fn main() {
let mut cache = SessionCache::new(100);
// Create sessions
cache.create_session("session-1".to_string(), "user-alice".to_string());
cache.create_session("session-2".to_string(), "user-bob".to_string());
// Use session
if let Some(session) = cache.get_session("session-1") {
println!("Session user: {}", session.user_id);
}
// Update session
cache.update_session("session-1", "theme".to_string(), "dark".to_string());
cache.update_session("session-1", "language".to_string(), "en".to_string());
// Check session data
if let Some(session) = cache.get_session("session-1") {
println!("Session data: {:?}", session.data);
}
// Delete session
cache.delete_session("session-1");
println!("Session deleted");
}LruCache::new(NonZeroUsize::new(capacity).unwrap())put(key, value) to insert, returns evicted item if capacity exceededget(&key) to retrieve and update access orderpeek(&key) to retrieve without updating access ordercontains(&key) to check existence without updating orderpop(&key) to remove and return a valueentry(key).or_insert(value), or_insert_with(), or_default()iter() (most to least recent) or iter().rev()resize(new_capacity) - evicts LRU items if shrinkingArc<Mutex<LruCache>>