cached
Rust caching structures and #[cached]/#[once]/#[concurrent_cached] macros for effortless function memoization.
Repository Health
Technical Analysis
cached is a Rust crate that provides both ready-made caching data structures (unbound, sized/LRU, TTL, LRU+TTL, sharded, disk-backed via redb, and Redis-backed) and a set of procedural macros — #[cached], #[once], and #[concurrent_cached] — that turn any function into a memoized one with a single attribute. Instead of hand-writing a HashMap-plus-Mutex around a function’s return value, developers annotate the function and choose an eviction policy, synchronization strategy, and optional backing store through macro arguments.
The crate is built around a small set of core traits (Cached, ConcurrentCached, their *Ext blanket-impl counterparts, and async mirrors like ConcurrentCachedAsync and CachedGetOrSetAsync) so that custom stores — including a user’s own third-party cache like moka — can be dropped in and still get the same short-alias method surface (get/set/remove/len) as the built-in stores. Feature flags gate everything beyond the in-memory default: redis_store plus a runtime flag (redis_tokio/redis_smol, optionally with TLS variants) enables async Redis-backed caches, and redb_store enables an embedded on-disk cache.
Synchronization is deliberately explicit rather than one-size-fits-all: #[cached] functions default to no write synchronization (matching Python’s functools.lru_cache and 2.x behavior), with opt-in sync_writes = "by_key" (per-key bucketed locks, tunable bucket count) or sync_writes = true (whole-cache lock) for use cases where duplicate concurrent computation is unacceptable. #[concurrent_cached] instead relies on the backing store’s own internal synchronization (per-shard parking_lot::RwLock for sharded stores, or the server/filesystem for Redis/disk).
What You Get
- Procedural macros
#[cached],#[once], and#[concurrent_cached]for one-line function memoization with configurable eviction, TTL, and synchronization - In-memory store implementations: unbound, sized/LRU, TTL, combined LRU+TTL, and sharded variants for reduced lock contention
- Optional Redis-backed store (
redis_storefeature) with sync and async (Tokio or async-std/smol) connection modes, including connection-manager and client-side caching variants - Optional disk-backed store via
redb(redb_storefeature) for persistent, embedded on-disk caching with sync and async access - Extension traits (
CachedExt,ConcurrentCachedExt,ConcurrentCacheBase) giving every store the same short method aliases (get/set/remove/len) via blanket implementations - Async trait surface (
ConcurrentCachedAsync,CachedGetOrSetAsync) for memoizing async functions and driving concurrent stores from async code - Extensive automated test suite covering concurrency semantics, eviction ordering, panic safety, and version-to-version parity for each store type
Common Use Cases
- Memoizing an expensive pure function (parsing, computation, serialization) with
#[cached]so repeated calls with the same arguments skip recomputation - Wrapping a slow or rate-limited external API call in a TTL-based cache so results are reused for a bounded window before refetching
- Building a request-scoped or process-wide LRU cache for hot lookups (e.g. config or user records) that need bounded memory via a fixed size limit
- Sharing a cache across async tasks or threads using a sharded or Redis-backed store so concurrent readers/writers don’t serialize on a single lock
- Persisting a cache to disk with the
redb_storefeature so warm data survives process restarts without standing up an external cache server
Under The Hood
Architecture
The crate centers on a small trait hierarchy in src/lib.rs: Cached and ConcurrentCached define the required cache_get/cache_set/cache_remove operations a store must implement, while blanket-implemented CachedExt/ConcurrentCachedExt extension traits layer short method aliases (get/set/remove) on top so every store, built-in or user-supplied, exposes the same ergonomic surface without duplicating code. Concrete stores live under src/stores/ (unbound, LRU via lru_list.rs, TTL, combined LRU+TTL, sharded, redb.rs for disk, redis.rs for the network-backed store), each satisfying the core traits independently, so a function annotated with #[cached] is decoupled from which store backs it. The #[cached]/#[once]/#[concurrent_cached] procedural macros (in the companion cached_proc_macro workspace crate) generate the boilerplate that wires a function to a chosen store and synchronization strategy at compile time, meaning the memoization logic itself never touches user code directly. Synchronization is handled explicitly at the store boundary — sharded stores hold per-shard parking_lot::RwLocks, while sync_writes = "by_key" routes through bucketed per-key locks in claim.rs — keeping contention scoped rather than relying on one global lock.
Tech Stack
The crate targets Rust 2024 edition with an MSRV of 1.92, built on hashbrown (with ahash hashing and OS-seeded runtime RNG on non-wasm targets) for its underlying maps and parking_lot for locking primitives. Feature flags cleanly separate optional capability: redis_store pulls in the redis crate (via r2d2 for sync pooling) plus serde/rmp-serde/serde_json for MessagePack serialization, while redb_store adds the embedded redb database and directories for platform-appropriate storage paths; both IO-backed stores additionally depend on async-lock when the async feature is enabled. Async runtime support is deliberately runtime-agnostic — redis_tokio/redis_smol (each with native-tls/rustls variants) let a consumer pick Tokio or async-std/smol without the crate hard-coding one. thiserror provides typed error variants, and the project targets wasm32-unknown-unknown as a first-class build target alongside native platforms.
Code Quality
Testing is extensive: the tests/ directory contains dozens of integration test files (300KB+ for the core cached.rs suite alone) covering eviction ordering, concurrency semantics under parking_lot, panic safety during cache misses, hasher/builder parity across API versions, and UI tests (via trybuild) asserting macro-misuse compile errors. CI (.github/workflows/build.yml) runs a dedicated MSRV job (cargo check --all-features pinned to Rust 1.92.0) plus a separate build job with rustfmt and clippy components and a wasm32-unknown-unknown target, so lint/format and cross-target compilation are both enforced. Error handling favors typed thiserror-derived errors over panics on the IO-backed paths, and the codebase carries detailed inline comments explaining non-obvious feature-flag interactions (e.g. why redis/connection-manager composes with either async runtime).
What Makes It Unique
Rather than picking one cache implementation and one memoization policy, cached exposes a uniform trait-based interface that spans in-memory, disk, and Redis-backed stores, sync and async call sites, and multiple explicit synchronization strategies (“no sync”, per-key bucketed locks, or whole-cache locks) that callers select per function rather than being forced into a single library-wide default. The sync_writes = "by_key" bucketed-lock design and the ability to wrap an arbitrary third-party concurrent cache (demonstrated with moka in the examples) behind the same trait surface reflect deliberate ergonomic and extensibility choices beyond what a typical single-policy memoization macro provides.