Flashvault logo

Rust · LRU cache in Rust · no dependency but clap

Every eviction,
in plain sight.

Flashvault is an LRU cache in Rust with a capacity bound, TTL expiry, and live hit-rate stats, all of it readable end to end. Set some keys below and watch the eviction order move.

0
Hits
0
Misses
0
Evictions
0%
Hit rate

most-recently-used first · red row is what the next insert would evict · state resets with the button, not on reload

How to use this playground
Type a key and value and hit Set to insert; the list reorders most-recently-used first. Get a key to see it jump to the top on a hit (or bump the miss count). Drag Capacity to change how many entries fit, and watch the red row, the next thing to be evicted, move as you go. Reset demo reseeds. It runs the same cache logic as the Rust crate, ported to JavaScript.

How it works

A HashMap for lookup and an intrusive doubly linked list for order, so both get and put stay O(1).

1

Slab of nodes

Entries live in a flat Vec of slots. A free list lets removed slots be reused instead of leaking space.

2

Intrusive list

Each node carries prev and next indices, so moving a key to the front on a hit is a pointer swap, not a scan.

3

Capacity bound

A put that would exceed capacity evicts the tail, the least-recently-used entry, before inserting.

4

TTL on read

Expiry is checked lazily at get time. An expired entry counts as a miss and an expiration, then is removed.

5

Injectable clock

A Clock trait separates real time from a settable TestClock, so TTL tests are deterministic, no sleeping.

6

Live stats

Hits, misses, evictions, expirations, and size are counted as they happen and read back with one call.

The stats it keeps, printed for real.

A canned workload at capacity 3: fill the cache, touch a key so it survives, insert past the bound so eviction fires, and let a short-lived entry expire. The final line is the exact stats the crate produces.

$ cargo run -- --capacity 3 demo
flashvault demo, capacity=3
filled cache to capacity (3 entries)
touched key0 -> value0 (now most-recently-used)
inserted one more key, size is still 3 (<= capacity 3)
short-lived key expired as expected
stats: hits=1 misses=1 evictions=2 expirations=1 size=2 hit_rate=0.50

Small enough to embed, not import.

There are excellent cache crates. Flashvault is the other option: one file you can read, own, and drop in when a large dependency is more than the job needs.

The lru crate

A solid, widely used LRU implementation. It does not carry TTL expiry or a stats surface, so those are yours to add on top.

moka, cached

Full-featured, concurrent, production caches. Powerful, and far more code and surface than a single-threaded readable cache calls for.

A bare HashMap

No recency, no bound, no expiry. Grows without limit and never evicts, which is exactly what a cache is supposed to do for you.

Flashvault

HashMap lookup, an intrusive list for O(1) recency, a hard capacity bound, lazy TTL expiry, an injectable clock, and live stats. Small enough to read end to end.

A library, a CLI, and a repl.

The same cache ships three ways: call it from Rust, run one-shot commands, or poke at it interactively.

Library

Build a Cache with a capacity and a Clock, then put with an optional TTL and get. Read stats() for hits, misses, evictions, expirations, size, and hit rate.

CLI

One-shot set and get, or demo to run the canned workload and print the stats it produces.

Repl

cargo run -- repl gives an interactive prompt: set, get, del, keys, stats, quit.

Tests

cargo test covers capacity-bound eviction, recency refresh on get, TTL expiry as a miss plus removal, stats correctness, and zero-capacity safety, using the settable TestClock so nothing sleeps.

# run the canned workload and print the stats it produces
cargo run -- --capacity 3 demo

# interactive repl: set, get, del, keys, stats
cargo run -- repl