Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
imc — In-Memory Cache
A trait-based, deduplicating, in-memory cache. One data copy per unique identity, even when the same record is fetched through different query arguments.
let user = through_imc;
let same = through_imc;
assert_eq!; // same backing entry
Quick Start
[]
= { = "https://github.com/gaurav1704/rust-imc" }
use ;
use Duration;
// ── 1. Define your type and configure caching ────────────────────────
// ── 2. Wrap expensive operations ─────────────────────────────────────
let u: User = through_imc;
let u2: User = through_imc;
assert_eq!; // one backing entry
Core API
CacheStrategy (enum)
| Variant | Eviction rule |
|---|---|
Lru |
Least Recently Used — evicts the entry with the oldest last_accessed timestamp |
Mru |
Most Recently Used — evicts the entry with the newest last_accessed timestamp |
Lfu |
Least Frequently Used — evicts the entry with the lowest access_count |
Mfu |
Most Frequently Used — evicts the entry with the highest access_count |
Fifo |
First In, First Out — evicts the entry with the earliest inserted_at |
ImcCacheable (trait)
Every cacheable type must implement this trait. You must define:
| Item | Type / Signature | Required? |
|---|---|---|
type Id |
Hash + Eq + Clone + Send + 'static |
yes |
type Key |
Hash + Clone + Send + 'static |
yes |
fn cache_id(&self) -> Self::Id |
— | yes |
fn cache_strategy() -> CacheStrategy |
— | no (default: Lru) |
fn cache_ttl() -> Option<Duration> |
— | no (default: None) |
fn cache_max_size() -> usize |
— | no (default: 10_000) |
fn cache_value_size() -> Option<usize> |
— | no (default: None) |
fn cache_max_value_size() -> usize |
— | no (default: 1_048_576) |
fn cache_invalidation_channel() -> Option<&'static str> |
— | no (default: None) |
use ;
use Duration;
CriticalKey (trait, requires critical feature)
Marker trait for key enums that should broadcast invalidation messages whenever [through_imc_keyed] stores a fresh value. Derive it — never implement manually:
use CriticalKey;
When the critical feature is enabled, through_imc_keyed requires T::Key: CriticalKey — you must derive CriticalKey on every key enum.
Functions
through_imc(args, || value) / through_imc_async(args, || async { value })
Cache with a free-form key (any Hash + Clone + Send + 'static). Two calls that produce the same identity share one entry:
let u: User = through_imc;
let same: User = through_imc;
assert_eq!;
Async variant (requires async or tokio feature):
let u: User = through_imc_async.await;
through_imc_keyed(key, || value) / through_imc_keyed_async(key, || async { value })
Same as through_imc but with a compiler-enforced key type:
through_imc_keyed;
// through_imc_keyed("anything", || …); // ✗ compile error
When the critical feature is enabled, broadcast is automatic (see Critical keys).
imc_remove::<T>(&id)
Remove a single entry by its identity:
;
imc_clear::<T>()
Evict every cached entry for type T:
;
imc_len::<T>() -> usize
Number of unique entries currently cached for type T.
imc_invalidation_id::<T>(&id) -> String
Compute the stable hash string for cross-process invalidation (see Redis cross-process invalidation).
Macros
log_event!
log_event!;
log_event!;
log_event!;
Requires the logging feature. Expands to nothing at compile time when the feature is off.
Feature Flags
| Feature | Default | Description |
|---|---|---|
| — | always | Core caching: through_imc, dedup, eviction, TTL |
async |
no | Enables through_imc_async / through_imc_keyed_async (runtime-agnostic) |
tokio |
no | Implies async + makes CacheWorker use tokio::task::spawn_blocking |
invalidation-redis |
no | Cross-process cache invalidation via Redis pub/sub |
critical |
no | Critical-key broadcast via Redis pub/sub. Implies invalidation-redis. Requires #[derive(CriticalKey)] on key enums used with through_imc_keyed |
logging |
no | Structured tracing events via log_event! macro |
metrics-prometheus |
no | Prometheus counters / gauges + HTTP /metrics endpoint |
Advanced Usage
Lifecycle
The global cache store initialises lazily on first use. For explicit control:
use Imc;
// Option A — just ensure the store exists (no background worker):
init;
// Option B — init + spawn a background maintenance worker:
let _worker = start;
Imc::start is equivalent to Imc::init() + CacheWorker::spawn().
Worker / Background Maintenance
use ;
use Duration;
let _worker = spawn_with_config;
While the worker is alive, inline eviction at set() is deferred to the periodic background sweep, keeping the hot path lock-free.
Redis cross-process invalidation
Requires: invalidation-redis feature.
[]
= { = "https://github.com/gaurav1704/rust-imc", = ["invalidation-redis"] }
= "0.27"
use ;
use Duration;
// ── 1. Configure invalidation channel on your type ────────────────────
// ── 2. Spawn worker + subscriber on every pod ─────────────────────────
let _worker = spawn_with_config;
// ── 3. On mutation, publish the stable hash ───────────────────────────
Key points:
- Every pod runs the subscriber (via
WorkerConfig::redis_connection_string). - Only the mutating pod calls
publish_invalidation— imc does not auto-publish. - The FNV-1a hash is deterministic across pods, so all pods agree on which entry to remove.
- Subscribers reconnect with a 5‑second backoff on error.
Multi-condition queries & Vec<T> result sets
Tuple args work as cache keys. The Vec<T> blanket impl caches entire filtered result sets:
use ;
use Duration;
// ── 1. Domain type (Id = i32) ────────────────────────────────────────
// ── 2. Single-row query with two conditions ───────────────────────────
// ── 3. Result set — Vec<User> implements ImcCacheable automatically ───
// cache_id() hashes all element IDs together. Two queries returning the
// same logical set of users share one cached Vec.
Per-value size limit
// Values larger than 64 KiB bypass the cache entirely:
let big = through_imc; // never stored
Typed keys
Override type Key with a closed enum for compiler-enforced cache keys:
use ;
through_imc_keyed;
through_imc_keyed;
// through_imc_keyed("anything", || …); // ✗ compile error
Critical keys (cross-pod broadcast)
Requires: critical feature.
When a critical value is recomputed on one pod, all other pods automatically evict their stale copy:
use ;
use CriticalKey;
// ── Pod A caches a user ────────────────────────────────────────────────
let u = through_imc_keyed;
// ── Pod B updates the same user via through_imc_keyed ──────────────────
// Because UserKey derives CriticalKey, the key hash is automatically
// published on the critical channel. Pod A receives it and evicts
// the stale entry. Next read on Pod A fetches fresh data.
let u = through_imc_keyed;
How it works:
- The
CriticalKeyderive macro generates a channel name frommodule_path!() + "::" + type_name. - When the
criticalfeature is enabled,through_imc_keyedrequiresT::Key: CriticalKey. After storing, it registers the channel and publishes the key hash. CacheWorker::spawn_with_configautomatically starts a Redis subscriber whenredis_connection_stringis set.- The subscriber removes the cache entry by key hash. Publishing is skipped when no subscriber is active.
Prometheus Metrics
Requires: metrics-prometheus feature.
Configuration
let _worker = spawn_with_config;
# prometheus.yml
scrape_configs:
- job_name: 'imc'
static_configs:
- targets:
Metric Reference
| Metric | Type | Description |
|---|---|---|
imc_cache_hits_total |
Counter | Successful get() returns |
imc_cache_misses_total |
Counter | Misses + TTL expirations |
imc_cache_sets_total |
Counter | Insertions (new + dedup) |
imc_cache_evictions_total |
Counter | Evictions to stay within cache_max_size() |
imc_cache_expired_total |
Counter | TTL-expired entries removed |
imc_cache_invalidation_received_total |
Counter | Cross-process invalidation messages received |
imc_cache_entries |
Gauge | Current unique entries (updated on imc_len() + sweep) |
Manual Access
// Encode as Prometheus text format:
let text = encode;
// Or start the HTTP server independently:
spawn;
Architecture
flowchart TD
A["through_imc(args, closure)"] --> B{"Read lock:<br/>args_hash → id_hash → Entry"}
B -->|"Hit + TTL fresh"| C["Return cached clone<br/>(bump access_count)"]
B -->|"Miss / expired"| D["Release read lock<br/>Execute closure → value"]
D --> E["Extract cache_id()<br/>→ id_hash (StableHasher)"]
E --> F["Write lock:<br/>PerTypeCache.set()"]
subgraph G["PerTypeCache::set()"]
H{"Existing entry<br/>at id_hash?"} -->|"Yes, fresh"| I["Update index only<br/>(dedup)"]
H -->|"No / expired"| J{"Cache full<br/>&&<br/>no worker?"}
J -->|"Yes"| K["Evict candidate<br/>(per strategy)"]
J -->|"No"| L["Skip eviction"]
K --> L
L --> M["Insert new Entry<br/>Update both maps"]
end
F --> N["Return canonical copy"]
subgraph O["Background Worker (opt-in)"]
P["CacheWorker::spawn()"] --> Q["Worker loop:<br/>recv_timeout(sweep_interval)"]
Q --> R{"Command?"}
R -->|"Remove(id)"| S["remove_data(id_hash)"]
R -->|"Clear(type)"| T["cache.clear()"]
R -->|"ClearAll"| U["stores.clear()"]
R -->|"Shutdown"| V["Exit"]
R -->|"Timeout"| W["sweep_all() →<br/>remove_expired()<br/>+ evict_to_max_size()"]
end
subgraph X["Cross-Process Invalidation (feature: invalidation-redis)"]
Y["Publisher pod:<br/>imc_invalidation_id::⟨T⟩(&id)"] --> Z["Stable hash → channel"]
Z --> AA["Redis Pub/Sub"]
AA --> AB["Subscriber thread<br/>(per pod)"]
AB --> AC["parse hash →<br/>remove_data(type_id, hash)"]
AB -.->|"record metric"| PE
end
subgraph PD["Prometheus Metrics (feature: metrics-prometheus)"]
PE["metrics::record_hit / record_miss /<br/>record_set / record_eviction /<br/>record_expired / record_invalidation_received<br/>set_entries"] --> PF["Local counters & gauges"]
PF --> PG["HTTP server on metrics_listen_addr<br/>GET /metrics → TextEncoder"]
PG --> PH["Prometheus scraper pulls<br/> /metrics endpoint"]
end
B -.->|"on hit"| PE
D -.->|"on miss"| PE
G -.->|"on set/evict/expiry"| PE
O -.->|"after sweep updates entries"| PF
O -.->|"Deferred eviction"| G
X -.->|"On mutation"| O
Module Structure
src/
├── lib.rs — Public re-exports, lifecycle (Imc::init / Imc::start)
├── traits.rs — ImcCacheable trait, CacheStrategy enum, CriticalKey trait
├── hasher.rs — StableHasher (FNV-1a), hash_value(), tick()
├── entry.rs — Entry struct with access metadata
├── cache.rs — PerTypeCache, GlobalCache, global()
├── worker.rs — CacheCmd, CacheWorker, worker_loop, sweep_all
├── api.rs — through_imc, through_imc_keyed, imc_remove, etc.
├── critical.rs — Critical-key registry, publisher, subscriber (behind critical)
├── invalidation.rs — Redis pub/sub subscriber (behind invalidation-redis)
├── log.rs — log_event! macro, component/action constants
├── metrics.rs — Prometheus counters, gauge, encode(), serve()
└── tests.rs — Unit tests (28+ across all feature combos)