Introdução: Por que cache em Rust?
\nVelocidade é a principal vantagem do Rust. Mas mesmo o código mais rápido pode esbarrar em entrada/saída (E/S) lenta: consultas a banco de dados, APIs externas ou cálculos de dados complexos. O cache resolve esse problema, armazenando resultados de operações caras em um armazenamento rápido. O Rust oferece ferramentas flexíveis para cache: desde estruturas de dados embutidas simples até poderosos sistemas distribuídos, como Redis e Memcached.
\nNeste artigo, abordaremos todos os níveis de cache em Rust: desde um simples HashMap até soluções profissionais com TTL (tempo de vida) e sincronização. Você aprenderá como escolher a ferramenta adequada para seu projeto e evitar erros comuns.
1. Cache embutido: HashMap e suas variações
\nA maneira mais simples de fazer cache em Rust é usar HashMap da biblioteca padrão. No entanto, em ambientes multithread, são necessários mecanismos adicionais de sincronização.
1.1. HashMap simples (single-thread)
\nPara aplicações single-thread, o HashMap comum é adequado. Exemplo:
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!("Valor em cache: {}", name);\n }\n}\nVantagens: código mínimo, sem dependências.
\nDesvantagens: sem TTL, não é thread-safe, memória ilimitada.
1.2. Cache thread-safe com Mutex e Arc
\nPara aplicações multithread, 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}\nDesvantagem: o bloqueio do Mutex pode reduzir o desempenho em alta concorrência.
\n\n1.3. Bibliotecas avançadas: lru e cached
\nPara código em produção, é melhor usar crates prontos:
\n- \n
lru— implementa o algoritmo de despejo LRU (Least Recently Used). Adequado para cache limitado. \n cached— fornece macros para cache automático de funções com suporte a TTL e LRU. \n
Exemplo com 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); // leva 2 segundos\n let cached_result = expensive_computation(5); // instantâneo\n println!("{}", cached_result);\n}\n\n2. Redis: cache distribuído para sistemas escaláveis
\nRedis é um armazenamento de dados in-memory, ideal para cache distribuído. Em Rust, usa-se o crate redis para trabalhar com Redis.
2.1. Conexão e operações básicas
\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 // Definir valor com TTL (tempo de vida)\n con.set_ex("user:1", "Alice", 3600)?; //