Library / SDK
zkat/cacache-rs avatar
zkat/cacache-rs

cacache: a content-addressable disk cache for Rust services

A high-performance, concurrent, content-addressable disk cache, with support for both sync and async APIs. 💩💵 but for your 🦀

719 stars44 forksRustNOASSERTION

At a glance

What is it?
cacache is a Rust library that stores blobs on disk keyed by a name, deduplicates identical content by hash, and verifies what it reads back, with async-std and tokio support and a std::fs-shaped API. It suits build systems, package managers and download caches, and it is a poor fit for a cache that needs eviction policies or a shared network store.
Who is it for?
cacache fits Rust services that need a durable, verified, deduplicating blob store on local disk, such as a build cache or a downloaded artefact cache, and its async-first API with a std::fs-shaped surface makes it straightforward to adopt in an existing tokio or async-std service. It is the wrong choice when you need TTL or LRU eviction, when several machines must share one cache over a network, or when you need a client library outside Rust.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 144 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A blob store addressed by hash, with a key pointing at it

cacache is described as a high-performance, concurrent, content-addressable disk cache, optimized for async APIs. The vocabulary of content addressing is the important part. Every piece of data is stored under a path derived from its hash, and a separate key is what your code actually uses to look data up. Two different keys that point at identical content resolve to the same file on disk.

That layout is what produces the deduplication the project lists as automatic content deduplication, and it is also what makes extraction by content address work, so you can hand cacache a shasum and have it find the bytes. The feature list also names multi-hash support, so sha1, sha512 and other digests can be hosted side by side in one cache directory, and Subresource Integrity support, which is the web standard for tying a downloaded resource to a cryptographic digest.

The API shape is deliberately familiar. The project advertises a std::fs-style API, and the example in the project documentation reads like a filesystem, with a directory, a key and a byte string, rather than like a typical cache client with typed handles and namespaces. The directory is the unit of isolation: the example passes ./my-cache as a plain string.

The intended users are the same people who wrote the Node.js original of the same idea: teams running package managers, build pipelines, container tooling or anything else that repeatedly fetches immutable artefacts and wants them on local disk with the guarantee that what it reads is what it wrote.

Verification and atomic writes are the point, not the speed

The feature list is where the design decisions become visible, and they point at durability rather than throughput. cacache claims fault tolerance, described as immunity to corruption, partial writes and process races. It claims atomic content writes even for large data, and consistency guarantees on read and write with full data verification. The practical meaning is that a read either returns the complete, correct bytes or fails, and a cache that was killed mid-write does not leave a half-written file that a later read would happily return.

Large file support is listed too, and the Cargo.toml shows how that is achieved: memmap2 is an optional dependency tied to the mmap feature, which is enabled by default. mmap is also where the libc dependency comes from, and it is listed only for Linux targets, so the memory-mapped path is platform dependent.

Two more entries deserve attention. Arbitrary metadata storage means you can attach your own data to a cache entry, which is how a package manager records which version a tarball corresponds to. Lockless, high-concurrency cache access is the claim that no reader or writer takes a global lock, so many processes can share one cache directory. The README also says cross-platform support, including Windows and case-insensitive or case-sensitive filesystem handling, which matters because a content-addressed path scheme can collide on a case-insensitive filesystem if the implementation is careless about it.

The error handling is called out as contextual, and miette is a hard dependency in Cargo.toml, integrated for detailed reporting. For a library that runs inside a build script, where the error message is often all the user sees, that is a deliberate choice.

Async first, with tokio as a feature flag rather than a fork

The runtime story is the most concrete engineering decision in the crate. async-std is the default runtime, and tokio is supported by turning off default features and enabling the tokio-runtime feature, which pulls in tokio, tokio-stream and futures. The Cargo.toml shows the optional dependency wiring for both, with tokio enabled for the fs, io-util, macros and rt features.

To use tokio, the documented dependency block is:

toml
[dependencies]
cacache = { version = "*", default-features = false, features = ["tokio-runtime", "mmap"] }

The project also supports removing async entirely, which is the more interesting option for a library that has to sit inside a synchronous build tool:

toml
[dependencies]
cacache = { version = "*", default-features = false, features = ["mmap"] }

Sync APIs are described as available but secondary, and the documentation notes that you can use them only and drop the async runtime dependency entirely. That is unusual and useful. Most Rust caching libraries force an async runtime on you, which is precisely the wrong dependency for a build script or a CLI that runs before anything else starts.

There is also an experimental feature for symlinking to existing files, called link_to, which is empty in the feature table, meaning it pulls in no extra dependencies. Experimental is the word the documentation uses, so treat it as something to test rather than rely on.

Reading, writing and clearing a cache from Rust

The example in the project documentation shows the whole cycle. The async attribute macro turns main into an async entry point, a directory is chosen as a plain string, data is written under a key, read back, and then the directory is cleared:

rust
use cacache;
use async_attributes;

#[async_attributes::main]
async fn main() -> Result<(), cacache::Error> {
    let dir = String::from("./my-cache");

    // Write some data!
    cacache::write(&dir, "key", b"my-async-data").await?;

    // Get the data back!
    let data = cacache::read(&dir, "key").await?;
    assert_eq!(data, b"my-async-data");

    // Clean up the data!
    cacache::rm::all(&dir).await?;
}

Three things are worth noting in that snippet. The error type is a single cacache::Error returned from main, so failures surface as a Result rather than as panics. The write takes a byte string, and the read returns the same bytes, which is why arbitrary metadata storage is a separate concern from the data itself. And the cleanup is a module call, cacache::rm::all, that removes everything under the directory rather than a single key.

Adding the crate to a project is done with cargo-edit:

bash
cargo add cacache

For a library that others depend on, the API reference lives on docs.rs at the docs.rs/cacache path, and the Cargo.toml points the package homepage at the repository.

The justfile runs everything twice, once per runtime

The repository ships a justfile, and its shape tells you what the project considers important. The test recipe is not a single cargo test but two full runs, one on the default async-std configuration and one with the runtime swapped:

bash
# Run tests on both runtimes with cargo nextest
@test:
    echo "----------\nasync-std:\n"
    cargo nextest run
    echo "\n----------\ntokio:\n"
    cargo nextest run --no-default-features --features tokio-runtime

The benchmark recipes follow the same pattern, with cargo bench and cargo criterion each run once per runtime, which means the crate's performance claims are checked on both backends rather than one. There is a benches/ directory in the tree, and the Cargo.toml registers a bench target named benchmarks with harness = false, which is the convention for criterion benchmarks.

The lint recipe runs cargo clippy --fix alongside cargo fmt, and the file also has a changelog recipe that pipes git-cliff output into CHANGELOG.md, and a release recipe that calls cargo release. The install-tools recipe lists the four workspace tools a contributor needs: cargo-nextest, cargo-release, git-cliff and cargo-criterion.

There is a Makefile.toml as well, plus cliff.toml for the changelog generator, an AGENTS.md at the root, and a CHANGELOG.md. The AGENTS.md file is a curiosity worth noting in a project that predates the widespread adoption of that convention, and it suggests the crate is maintained with the same kind of agent-assisted workflow that the file format is designed for.

No eviction, and that is a design decision rather than a gap

The most important limitation is the one that is not mentioned anywhere. The feature list contains no entry for TTL, no entry for LRU, no entry for maximum size and no entry for capacity limits. There is also no eviction anywhere in the dependency list, no clock crate and no background task runtime for a reaper, and the documented cleanup path is a call that removes everything under a directory. cacache::rm::all is a sweep, not a policy.

This matters more than a missing convenience feature. A content-addressable store deduplicates by construction, so the natural way it grows without bound is a long-running service that writes many distinct artefacts. With no eviction, the only way to bound disk usage is to delete the cache yourself, by directory, on your own schedule. For a CI cache or a build cache that is often the right answer anyway, since the value of the cache depends on the build inputs that produced it. For a long-lived service downloading user-supplied content, it means you are writing your own retention logic on top.

The second limitation is that this is a local disk cache with a filesystem-shaped API. Lockless concurrency is claimed, which covers many processes on one machine, but nothing in the crate coordinates across machines. A shared network cache is a different design with different consistency costs, and cacache does not attempt it. A team that needs one cache served to many hosts is looking at a different tool and should not try to bolt a network filesystem underneath this one, since the locking assumptions would no longer hold.

The third is the API surface's Rust specificity. There is no HTTP interface and no foreign function interface layer. A build system written in another language cannot call into it directly, which limits it to Rust processes and to tools that can shell out to a helper binary.

Version numbers and a contradictory minimum Rust version

The release history is worth reading closely. The three most recent published tags are v13.0.0, v12.0.0 and v11.7.1, and all three carry the same timestamp of 2024-02-15, differing only in the seconds. That pattern is the signature of a bulk re-tag or a publishing sweep rather than three separate releases, and it means the tag dates do not tell you when the work actually happened. The Cargo.toml, by contrast, records version 13.1.0, so the manifest is ahead of the newest published tag.

Combined with the last push on 2026-06-01, the picture is a crate whose working tree has moved on since the last tag on crates.io. That is not a criticism so much as a fact to plan around: depending on 13 from crates.io gives you the code as of that 2024 tag, not the current main branch.

The minimum supported Rust version is stated twice, and the two statements disagree. The install section says the minimum supported Rust version is 1.43.0, while the dedicated MSRV section says it is 1.67.0 and adds that any change to the MSRV will be treated as a breaking change. The 1.67.0 figure is the one to believe, since the section that declares a policy around it is the more deliberate statement, and the Cargo.toml declares edition 2021, which itself requires a modern compiler. The 1.43.0 number is a leftover.

The licence is Apache-2.0 according to both the Cargo.toml and the README, and the file is LICENSE.md in the repository root. Apache-2.0 carries the usual patent grant and requires that notices be preserved when you redistribute, which for a library usually means keeping the licence text alongside your own distribution, but check the file itself rather than taking a summary as the terms.

Editorial conclusion

cacache fits Rust services that need a durable, verified, deduplicating blob store on local disk, such as a build cache or a downloaded artefact cache, and its async-first API with a std::fs-shaped surface makes it straightforward to adopt in an existing tokio or async-std service. It is the wrong choice when you need TTL or LRU eviction, when several machines must share one cache over a network, or when you need a client library outside Rust. Before adopting it, confirm your Rust toolchain meets the stated MSRV of 1.67.0, run cargo nextest run on both runtimes as the justfile does, and check the licence file yourself, because the repository ships Apache-2.0 in Cargo.toml while the last push was on 2026-06-01 and the newest published release tag is v13.0.0.

Frequently asked questions

How do I add cacache to a Rust project?

The project documents cargo-edit for the purpose, so the command is cargo add cacache. The crate is published on crates.io as cacache, and its API documentation is on docs.rs.

Can I use cacache with tokio instead of async-std?

Yes. async-std is the default runtime, and for tokio you turn off default features and enable the tokio-runtime feature, which pulls in tokio, tokio-stream and futures. You can also drop the async runtime entirely and use the sync APIs only.

Does cacache deduplicate identical content?

The feature list includes automatic content deduplication, since content is stored under a path derived from its hash, and it also supports extraction by content address with multiple hash algorithms hosted in one cache.

Does cacache evict old entries automatically?

No eviction policy is listed among the features, and the documented cleanup is a removal of everything under the cache directory. Bound the disk usage yourself by deleting the directory on your own schedule.

What Rust version does cacache require?

The MSRV section states 1.67.0 and says changes to it count as breaking changes, while the install section still mentions 1.43.0. The Cargo.toml declares edition 2021, so plan for a 1.67.0 or newer toolchain.

Official sources

  1. Issues
  2. Project website
  3. README
  4. Releases
  5. zkat/cacache-rs on GitHub
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/zkat-cacache-rs.svg)](https://hysenlabs.com/projects/zkat-cacache-rs)

Was this useful?