Caching in Rust: Redis, Memcached, and Built-in Solutions
Caching is one of the key mechanisms for optimizing the performance of modern applications. In the Rust ecosystem, known for its speed and memory safety, choosing the right caching tool can significantly impact program efficiency. In this article, we will look at three main approaches: external systems Redis and Memcached, as well as built-in solutions available in the standard library and popular crates.
Why cache in Rust?
Rust itself is already very fast, but when working with databases, external APIs, or complex computations, caching allows you to:
- Reduce latency on repeated requests
- Decrease load on databases and external services
- Save computational resources and CPU time
1. Built-in Solutions: HashMap and LRU Cache
The simplest way to cache in Rust is using HashMap from the standard library. For multithreaded applications, a Mutex<HashMap> or RwLock<HashMap> wrapper is required. However, this approach lacks an eviction policy, which can lead to infinite memory growth.
Example of a simple cache using HashMap
use std::collections::HashMap;use std::sync::Mutex;
struct SimpleCache { store: Mutex<HashMap<String, String>>,}
impl SimpleCache { fn new() -> Self { SimpleCache { store: Mutex::new(HashMap::new()), } }
fn get(&self, key: &str) -> Option<String> { self.store.lock().unwrap().get(key).cloned() }
fn set(&self, key: String, value: String) { self.store.lock().unwrap().insert(key, value); }}LRU Cache via the lru crate
To limit the cache size and automatically evict old entries, use the lru crate. It implements the Least Recently Used algorithm — removing the least frequently used items.
use lru::LruCache;use std::num::NonZeroUsize;
fn main() { let mut cache = LruCache::new(NonZeroUsize::new(3).unwrap()); cache.put("apple", 10); cache.put("banana", 20); cache.put("cherry", 30); cache.put("date", 40); // "apple" will be evicted
println!("{:?}", cache.get(&"apple")); // None println!("{:?}", cache.get(&"date")); // Some(40)}Pros: zero network latency, simplicity, full control. Cons: cache lives only within a single process, no persistence.
2. Caching with Redis via redis-rs
Redis is an in-memory key-value store, often used as a distributed cache. In Rust, the redis crate (or redis-rs) is used to work with Redis.
Connection and basic operations
use redis::Commands;
fn main() -> redis::RedisResult<()> { let client = redis::Client::open("redis://127.0.0.1:6379/")?; let mut con = client.get_connection()?;
// Set value with TTL (time to live) con.set_ex("user:42", "{\\"name\\": \\"Alice\\"}", 60)?;
// Read let value: String = con.get("user:42")?; println!("{}", value);
// Check existence let exists: bool = con.exists("user:42")?; println!("Exists: {}", exists);
Ok(())}Advanced Redis features
- TTL (Time To Live): automatic deletion of expired entries
- Data structures: lists, sets, hashes — for complex scenarios
- Pub/Sub: notifications about cache changes
- Clustering: horizontal scaling
Pros: dis