zstd-rs wraps the C compressor with sized and streaming APIs
A rust binding for the zstd compression library.
At a glance
- What is it?
- The zstd crate is a Rust binding for the zstd compression library that adds a frame size aware API and convenience wrappers over Read and Write. It also ships the C library as a submodule, so the build story is part of the adoption decision.
- Who is it for?
- The zstd crate fits a Rust project that wants zstd compression with a safe API and does not want to link a system library, particularly one that streams large files or compresses to a Vec. It does not fit a project pinned to a very old Rust compiler, since rust-version is 1.64, or one that needs a wasm target to gain anything from the wasm feature, which does nothing.
- Can I use it commercially?
- Yes. BSD-3-Clause 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 2 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 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A binding to the real zstd library, with the C source included
The zstd crate is a Rust binding for the zstd compression library, not a reimplementation of it. The C library is included as a submodule inside the repository, and the build compiles it from source unless you ask otherwise. The package metadata describes it as a binding for the zstd compression library, categorised under compression and api-bindings, with the crate published as zstd and documentation hosted on docs.rs. Two crates do the work: the top level zstd crate that most users depend on, and a companion zstd-safe crate that the zstd crate itself depends on for the raw interface, declared with default features off and the std feature on. Versions move separately, with the zstd-safe dependency pinned at 8.2.0 while the zstd package itself sits at 0.14.3. The last push was on 2026-10-21 and the recent tags include v0.14.2 on 2026-10-10 and v0.14.0 on 2026-09-04.
Adding the dependency and running the first compress
Adding the crate is a cargo add, and the manifest entry it produces is pinned to the 0.14 line:
$ cargo add zstd# Cargo.toml
[dependencies]
zstd = "0.14"The usage model is a pair of Read and Write wrappers, plus convenience functions for common jobs. The shortest path is a function that copies standard input to standard output through the stream helpers:
fn decompress() {
zstd::stream::copy_decode(io::stdin(), io::stdout()).unwrap();
}The same thing can be written by hand with an Encoder, which matters when you want the encoder to be finished explicitly rather than dropped at the end of the copy. For a file you would rather compress in one shot, the bulk functions avoid streaming buffers entirely. Which of these to reach for is the subject the documentation spends most of its space on, and the reason is the frame header.
The frame header carries the decompressed size
A zstd frame can record how large the data will be once decompressed, and whoever reads it can then size their buffer in one go instead of growing it. The documentation puts the effect at roughly a factor of two for the reader. Only a compressor that knows the length up front can write that value, so the choice of function determines whether the header carries it. Compressing a slice or a Vec uses bulk::compress into a Vec, or compress_sized_into into a Write. A file goes through compress_file or compress_file_into. A Read whose length you know uses compress_sized, and compress_sized_into for a Write destination. Anything else falls to encode_all or copy_encode, and those record the size only when the source turns out to be shorter than 128 KiB. On the way back, decode_all reads the header and sizes the output for you, and decompressed_size gives the same number for a buffer you own.
Four reasons your measured numbers look worse than zstd -b3
The wrapper is supposed to add little over the C library, and at compression level 3 a re-used bulk::Compressor and bulk::Decompressor land within a few percent of what zstd -b3 reports for the same file. The documentation lists the usual explanations when your own numbers fall short. A debug build is the first suspect, since it times the crate's wrappers rather than zstd, so always measure with release enabled. The second is growing a Vec instead of sizing it, since the convenience functions that return a Vec start from an empty one, and sizing the output up front was worth roughly a factor of two in the project's own measurements. The third is streaming data you already hold in memory, where the one-shot bulk API avoids a copy through the streaming buffers, worth about ten percent on decompression. The fourth is leaving compression single threaded, where the zstdmt feature with several workers made compression of a 33 MB input go about three times faster, though it is a loss on small inputs. The repository ships a throughput example to reproduce these comparisons on your own data.
Cargo features decide how the C library is built
Three features are on by default: legacy, arrays and zdict_builder. Legacy decodes frames written by zstd versions older than 0.8, arrays lets fixed size arrays serve as output buffers, and zdict_builder trains new dictionaries, while using a dictionary always works. Optional switches include experimental, which exposes zstd's experimental API with no stability guarantees and possible changes between zstd releases, zstdmt for multi threaded compression, thin for a smaller C library at some cost in speed and error reporting, no_asm to skip the x86-64 assembly, and doc-cfg to mark feature gated items in generated documentation, which needs a nightly compiler. A separate group changes where the C library comes from: bindgen generates bindings at build time and needs libclang, pkg-config links against a system installed libzstd, vendored forces the bundled source to build even when pkg-config is on, and cmake uses zstd's own CMake files instead of the cc crate, which handles cross compilation better but needs cmake. The wasm feature is listed as doing nothing and existing only so current dependents keep building.
Cloning the C source correctly
Getting the repository is the step that catches people out, because the C library lives in a submodule and a plain clone leaves it empty. The documented command pulls everything in one go:
git clone https://github.com/gyscos/zstd-rs --recursiveIf you already cloned without the recursive flag, the fix is one command from inside the repository:
git submodule update --initAfter that a cargo build should take care of building the C library. For anyone depending on the crate rather than working in the repository, this only matters for building from source, and the pkg-config feature exists precisely to avoid compiling the bundled copy when a system library is acceptable. The zstd-sys readme covers build time environment variables and what to do when the linker complains about a hidden zstd symbol being referenced by DSO, which is the symptom that appears when linking a shared library built against a static one.
Where this binding sits against the C API and async runtimes
The alternative to this crate is to call the C library through your own FFI layer, which means writing the bindings, managing the context lifetimes and choosing the libzstd your build links against by hand. The difference in approach is that the crate does that work and adds a Rust shaped API on top, where the size aware functions and the Read and Write wrappers are not in the C library but in this wrapper. The other option is to skip zstd specific code entirely and go through async-compression, which the documentation points to as an async ready integration of various compression algorithms including zstd-rs. That crate is the route for a Tokio or async runtime, where blocking stream wrappers are the wrong shape, and the zstd crate sits underneath it either way.
Editorial conclusion
The zstd crate fits a Rust project that wants zstd compression with a safe API and does not want to link a system library, particularly one that streams large files or compresses to a Vec. It does not fit a project pinned to a very old Rust compiler, since rust-version is 1.64, or one that needs a wasm target to gain anything from the wasm feature, which does nothing. Verify first that your build environment can compile the bundled C library, decide between the default features and pkg-config before you depend on it, and measure with a release build, since a debug build times the wrappers rather than zstd.
Frequently asked questions
How do I add the zstd crate to a Rust project?
Run cargo add zstd, which adds zstd = "0.14" to the dependencies table in Cargo.toml. The crate bundles the zstd C library as a submodule, so building from a clone requires git clone with the recursive flag or a git submodule update --init afterwards.
Which functions should I use to compress a large file in Rust?
Use compress_file for a file into a Vec, or compress_file_into when you would rather stream into a Write and avoid holding the whole output in memory. Both record the decompressed size in the frame header, unlike encode_all and copy_encode which only do so for sources under 128 KiB.
Why is my zstd Rust compression slower than the zstd command line?
The documentation lists a debug build, growing a Vec instead of sizing it, streaming data already held in memory, and leaving compression single-threaded as the usual causes. Measure with a release build, size the output up front, use the bulk API for in-memory slices, and consider the zstdmt feature for large inputs.
Can I link against a system libzstd instead of the bundled one?
Yes, the pkg-config feature links against a system installed libzstd instead of building the bundled source, and the vendored feature forces the bundled source to build even when pkg-config is enabled. The cmake feature builds the C library with zstd's own CMake files and needs cmake installed.
Official sources
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.
[](https://hysenlabs.com/projects/gyscos-zstd-rs)Was this useful?