Introduction: Why Caching in Rust?
\nSpeed is Rust's main advantage. But even the fastest code can hit a bottleneck with slow I/O: database queries, external APIs, or complex data computations. Caching solves this problem by storing the results of expensive operations in fast storage. Rust offers flexible tools for caching: from simple built-in data structures to powerful distributed systems like Redis and Memcached.
\nIn this article, we'll cover all levels of caching in Rust: from a simple HashMap to professional solutions with TTL (time-to-live) and synchronization. You'll learn how to choose the right tool for your project and avoid common mistakes.
1. Built-in Caching: HashMap and Its Variants
\nThe simplest way to cache in Rust is to use HashMap from the standard library. However, in a multithreaded environment, additional synchronization mechanisms are needed.
1.1. Simple HashMap (Single-threaded)
\nFor single-threaded applications, a regular HashMap works. Example:
use std::collections::HashMap;\n\nfn main() {\n let mut cache: HashMap<String, String> = HashMap::new();\n cache.insert("user:1".to_string(), "Alice".to_string());\n \n if let Some(name) = cache.get("user:1") {\n println!("Cached value: {}", name);\n }\n}\nPros: minimal code, no dependencies.
\nCons: no TTL, not thread-safe, memory is unbounded.
1.2. Thread-safe Cache with Mutex and Arc
\nFor multithreaded applications, use Arc<Mutex<HashMap>>:
use std::collections::HashMap;\nuse std::sync::{Arc, Mutex};\nuse std::thread;\n\nstruct Cache {\n store: Arc<Mutex<HashMap<String, String>>>,\n}\n\nimpl Cache {\n fn new() -> Self {\n Cache { store: Arc::new(Mutex::new(HashMap::new())) }\n }\n \n fn get(&self, key: &str) -> Option<String> {\n self.store.lock().ok()?.get(key).cloned()\n }\n \n fn set(&self, key: String, value: String) {\n self.store.lock().unwrap().insert(key, value);\n }\n}\n\nfn main() {\n let cache = Cache::new();\n let c1 = cache.clone();\n thread::spawn(move || {\n c1.set("key".to_string(), "value".to_string());\n });\n println!("{:?}", cache.get("key"));\n}\nDrawback: Mutex locking can reduce performance under high contention.
\n\n1.3. Advanced Libraries: lru and cached
\nFor production code, it's better to use ready-made crates:
\n- \n
lru— implements the LRU (Least Recently Used) eviction algorithm. Suitable for bounded caches. \n cached— provides macros for automatic function caching with TTL and LRU support. \n
Example with cached:
use cached::proc_macro::cached;\n\n#[cached(size = 100, time = 300)]\nfn expensive_computation(x: u32) -> u32 {\n std::thread::sleep(std::time::Duration::from_secs(2));\n x * x\n}\n\nfn main() {\n let result = expensive_computation(5); // takes 2 sec\n let cached_result = expensive_computation(5); // instant\n println!("{}", cached_result);\n}\n\n2. Redis: Distributed Cache for Scalable Systems
\nRedis is an in-memory data store, ideal for distributed caching. In Rust, the redis crate is used to work with Redis.
2.1. Connection and Basic Operations
\nuse redis::Commands;\n\nfn main() -> redis::RedisResult<()> {\n let client = redis::Client::open("redis://127.0.0.1:6379/")?;\n let mut con = client.get_connection()?;\n \n // Setting a value with TTL (time-to-live)\n con.set_ex("user:1", "Alice", 3600)?; //