Open-source project
jeromefroe/lru-rs avatar
jeromefroe/lru-rs

lru-rs: a small bounded LRU cache with O(1) put, get and pop

An implementation of a LRU cache

845 stars135 forksRustMIT

At a glance

What is it?
The lru crate is a single-purpose Rust implementation of a least recently used cache, where every operation the README lists runs in O(1). It is deliberately narrow: no expiry, no async, no background eviction, and a capacity type that will not let you build an empty cache.
Who is it for?
Adopt lru-rs when you need a fixed size cache with strict O(1) access and eviction, on a Rust version of 1.85.0 or later, and you are content to manage expiry yourself. Do not adopt it for a cache that must expire entries on a timer, run across threads without an external lock, or be shared between async tasks, since the README documents none of those.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 10 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Four operations, all documented as O(1)

The whole public surface of the crate is small enough to state in one sentence. The README says the cache supports put, get, get_mut and pop operations, all of which are O(1). There is no iterator over the entries in the example, no capacity setter, no clear, and no resize. That narrowness is the design, not an omission. The crate is a bounded cache with a fixed capacity, where the least recently used entry is dropped once the bound is exceeded, and the value of a dependency like this is that you can predict what it will cost on every call. The project states that it was heavily influenced by the LRU cache implementation in an earlier version of Rust's std::collections crate, which is a useful reference point: the idea has been in the ecosystem long enough to have been tried, and this crate takes that shape and ships it as a standalone library rather than as part of the standard library.

Capacity is a NonZeroUsize, so an empty cache cannot be built

The constructor in the example takes a NonZeroUsize rather than a plain integer:

rust
    let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());

That is the type system pushing back on a degenerate case. A cache with capacity zero has no coherent behaviour, and a plain usize would let you create one by accident. Using NonZeroUsize means the compiler rejects it at the call site, and the unwrap in the example is only there because the example has to get a non-zero value from somewhere. The same reasoning runs through the rest of the API. A miss on get returns None rather than a default, and a put that replaces an existing value returns the previous value as Some, while a put that inserts a new key returns None. You can therefore tell an insert from a replacement by matching on the return, without a separate contains check, and every one of those returns is a branch you can take rather than a lookup you have to make.

Reading, mutating in place, and removing entries

get_mut is the operation that separates this from a plain map wrapper. The example takes a mutable reference to the stored value, mutates it in place, and then reads the value back:

rust
    {
        let v = cache.get_mut(&"banana").unwrap();
        *v = 6;
    }

The braces are there for a reason. The mutable borrow of the value has to end before the next call to get, and the scoped block makes that end explicit. Without it, a borrow checker error would stop the program. For a cache that holds a value you want to update in place, this avoids a remove-and-reinsert dance, which would also change the recency ordering as a side effect. pop is the removal operation, and the README lists it among the O(1) methods. Together, these four methods cover the realistic uses: read, write, update in place, and evict deliberately.

Recency order is what eviction follows, and the example proves it

The README example is short, and it doubles as a specification of the eviction rule. The cache is created with a capacity of two. An apple and a banana are put, and both are then read, which makes them recently used. A put of banana with a new value returns the old value, and a put of pear returns None since pear was not present. After that, the example asserts that pear is present and banana is present, and that apple is gone. Apple was the least recently used entry once banana and pear had been written, so it was the one dropped. The recency order is not merely insertion order, since a get also refreshes an entry, and the example reads apple and banana before the third put specifically to set that up. This is the behaviour to test against if you are porting from a cache that evicts on write order only, because the two policies produce different survivors under mixed read and write traffic.

hashbrown is the optional backend, and the default feature set is tiny

The manifest declares one optional dependency. hashbrown comes in at version 0.17.0 with default-features turned off and the default-hasher and inline-more features enabled, and it is optional, which means it can be turned off. The features table lists default = ["hashbrown"], plus a nightly feature that pulls in hashbrown and hashbrown/nightly. The category list in the manifest includes no-std, so the crate is positioned for use without the standard library. The dev-dependencies are notable for what they reveal about the testing approach: scoped_threadpool and stats_alloc. The first suggests the tests exercise concurrent access patterns, and the second suggests the tests measure allocations rather than only checking behaviour. Both are dev-only, so neither affects what you pull into a production build.

No expiry, no shared ownership, no async support

Three things you might expect from a cache are absent, and none of them are hidden. There is no time-based expiry: the README documents no TTL, no clock and no background reaper, so an entry lives until it is pushed out by capacity pressure. There is no built-in concurrency. The example creates the cache as a plain local variable and calls put on it through a mutable borrow, and the manifest carries no locking or atomic types, so sharing a cache across threads means wrapping it in a Mutex yourself, and the O(1) guarantee survives that, but the lock does not. There is also no async surface. The operations are synchronous, which is fine for a cache in a synchronous function, and is why a Tokio or async-std user ends up calling them inside a blocking section or with a dedicated thread. Whether any of that is a problem depends entirely on the workload, but the README does not claim these features, so you should not design around them.

MIT licensed, version 0.19.0, and no tagged releases

The licence is MIT, stated in both the manifest and the LICENSE file, with no dual-licence option and no third-party code in the dependency tree to complicate the obligations. The crate is published on crates.io under the name lru, with the package page at crates.io/crates/lru and the API documentation on docs.rs, both linked from the README. The version in the manifest is 0.19.0 and the minimum supported Rust version is 1.85.0, which the README states as the MSRV for the crate. Because the version lives below 1.0, semver treats minor releases as potentially breaking, so a 0.18 to 0.19 bump is the kind of change to read about rather than absorb silently. On maintenance, the repository is not archived and the last push was on 2026-10-18, so the code is moving. The repository has no GitHub releases, which means there is no tag to point at in a dependency declaration and no release notes attached to a version. The CHANGELOG.md file at the root is where the upgrade history lives instead.

Editorial conclusion

Adopt lru-rs when you need a fixed size cache with strict O(1) access and eviction, on a Rust version of 1.85.0 or later, and you are content to manage expiry yourself. Do not adopt it for a cache that must expire entries on a timer, run across threads without an external lock, or be shared between async tasks, since the README documents none of those. Before you commit, confirm your Rust toolchain meets the 1.85.0 MSRV, and note that the repository has no GitHub releases, so the version 0.19.0 declared in Cargo.toml is what you pin and CHANGELOG.md is where upgrade notes live.

Frequently asked questions

What is the lru crate in Rust?

It is an implementation of a LRU cache, published on crates.io as lru and licensed under MIT. The cache supports put, get, get_mut and pop operations, all of which are documented as O(1).

How do I create an lru::LruCache in Rust?

You construct it with a NonZeroUsize capacity, for example LruCache::new(NonZeroUsize::new(2).unwrap()). The capacity type is NonZeroUsize so that a cache with zero capacity cannot be created by mistake.

What Rust version does the lru crate require?

The README states that the MSRV for the crate is 1.85.0, and the manifest repeats the same value in the rust-version field. The manifest also lists a nightly feature alongside the default hashbrown feature.

Does the lru crate expire entries automatically?

Nothing in the README describes time-based expiry, a TTL, a clock or a background reaper, and the listed operations are put, get, get_mut and pop. An entry is removed when the cache is over capacity and that entry is the least recently used.

Official sources

  1. Issues
  2. jeromefroe/lru-rs on GitHub
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jeromefroe-lru-rs.svg)](https://hysenlabs.com/projects/jeromefroe-lru-rs)

Was this useful?