从源码克隆需加 --recursive 或执行 git submodule update --init 拉取 zstd 子模块;性能测试必须用 --release,debug 构建测的是包装层而非 zstd;启用 bindgen 特性需 Rust 1.71+ 与 libclang,rust-backend 需 Rust 1.89+ 且与 seekable 特性冲突
zstd-rs
gyscosBSD-3-Clause活跃维护Rust 对 zstd 压缩库的绑定,让 Rust 程序能直接调用 zstd 做高速压缩和解压,省去自己写 FFI 的麻烦。
A rust binding for the zstd compression library.
cargo add zstdREADME 未说明显存或 GPU 要求;为纯 CPU 压缩库,支持 Linux/Windows/macOS 及 wasm 构建。
zstd 压缩库的 Rust 绑定,提供 Read/Write 包装与 bulk 一次性 API
Rust 对 zstd 压缩库的绑定,让 Rust 程序能直接调用 zstd 做高速压缩和解压,省去自己写 FFI 的麻烦。
核心能力
- 提供 Read/Write 流式编解码包装与 copy_encode/copy_decode 便捷函数
- bulk 一次性 API 对已知长度的切片避免流式缓冲拷贝
- 压缩时记录解压后大小,解压端可一次性分配输出缓冲
- zstdmt 特性开启 C 库多线程压缩
- 可选 rust-backend 用纯 Rust 移植替代 C 库
容易踩的坑
- v0.14.2最新正式版2026-10-10 · 2026-10-10 13:52 UTC
zstd 0.14.2
This release adds decoder APIs that let archive libraries recover their readers without consuming more compressed input or losing ownership on initialization errors.
- Add
stream::read::Decoder::into_inner(), which returns the underlying reader at its current position without additional I/O. This supports raw copying of compressed archive entries. Addresses #381 and the regression reported in #380. - Add
Decoder::try_new,try_with_buffer,try_with_dictionary,try_with_prepared_dictionary, andtry_with_ref_prefix. These returnResult<Self, (R, io::Error)>, preserving the original reader if decoder initialization fails. Addresses #382. - Return an I/O error if a streaming decoder cannot allocate its decompression context, instead of panicking.
- Clarify that
finish_frame()can stop before the end of a frame when decoded output remains unread.
Decoder::finish()keeps its 0.14 behavior: it attempts to consume remaining frame input before returning the reader. Archive libraries that need to recover compressed bytes should useinto_inner()and adopt the newtry_*constructors where they need to retain the reader after initialization failure. Existing constructor signatures remain unchanged.Rust 1.64 remains supported.
- Add
- v0.14.02026-09-04 · 2026-09-04 19:04 UTC
查看更新说明
Breaking
- The
with_prepared_dictionary()constructors now borrow the dictionary for as long as the stream (https://github.com/gyscos/zstd-rs/pull/372). The dictionary is registered with the underlying context by pointer, so dropping it early left the stream with a dangling pointer - reachable from safe code. - The project is now licensed under BSD-3-Clause (https://github.com/gyscos/zstd-rs/pull/360, https://github.com/gyscos/zstd-rs/issues/314).
- Requires zstd-safe 8.0.0.
Fixed
Decoder::finish()now consumes the rest of the frame, so the reader is handed back positioned just after it (https://github.com/gyscos/zstd-rs/pull/251, https://github.com/gyscos/zstd-rs/issues/255). zstd can produce the last of the decoded data before it has read the frame epilogue, so a caller who stops reading once it has the bytes it wanted used to get its reader back pointing inside the frame. Note thatfinish()may now read from the underlying reader;Decoder::finish_frame()is the same operation with the error surfaced.Read::read()with an empty buffer returnsOk(0)instead of an error (https://github.com/gyscos/zstd-rs/issues/318).
New
Decoder::finish_frame(), to consume the rest of the current frame explicitly.bulk::Compressor::set_pledged_src_size()(https://github.com/gyscos/zstd-rs/issues/320).- The missing
with_contextconstructors (https://github.com/gyscos/zstd-rs/pull/338). cmakeandvendoredfeatures, forwarded tozstd-safe.
- The
- zstd-safe-8.0.0zstd-safe v8.0.02026-09-04 · 2026-09-04 19:04 UTC
查看更新说明
Breaking
CCtx::ref_cdict()andDCtx::ref_ddict()now borrow the dictionary for as long as the context. zstd stores the dictionary in the context by pointer and dereferences it on every later operation, so safe code could previously drop the dictionary and cause a use-after-free.AdvancedSeekableno longer implementsDerefMut(https://github.com/gyscos/zstd-rs/issues/366). Handing out a&mut Seekablelet safe codemem::swap()the context out and leave the swapped-out one holding a pointer to a reader this type frees on drop.decompress()anddecompress_frame()are available directly onAdvancedSeekableinstead, andDerefstill covers the read-only queries.
Fixed
- A context is no longer reused after an error left it in a state zstd considers undefined (https://github.com/gyscos/zstd-rs/issues/315). zstd documents calling the streaming functions again on such a context as undefined behaviour; they now return the original error until something resets the context.
Cursorpositions are no longer truncated when converted tousize, which on a 32-bit target could turn an out-of-range position into a small in-range one and read or write the wrong part of the buffer.Seekable::init_advanced()no longer leaks the boxed reader when initialization fails.
Other
- New
cmakeandvendoredfeatures, forwarded tozstd-sys. ResetDirectivederivesCopy,Clone,Debug,PartialEqandEq, like the crate's other parameter enums.- The
SendandSyncimpls carry safety comments explaining why they hold.
- zstd-sys-2.1.0zstd-sys v2.1.02026-09-04 · 2026-09-04 19:04 UTC
查看更新说明
Breaking
- The
bindgenfeature is no longer enabled by default (https://github.com/gyscos/zstd-rs/pull/371). The pre-generated bindings are used unless you opt in, solibclangis no longer needed for a default build. This makes hermetic and cross builds much easier (https://github.com/gyscos/zstd-rs/issues/370). - The project is now licensed under BSD-3-Clause (https://github.com/gyscos/zstd-rs/pull/360, https://github.com/gyscos/zstd-rs/issues/314).
New
cmakefeature: build the C library with zstd's own CMake files instead ofcc, which handles cross-compilation better (https://github.com/gyscos/zstd-rs/issues/229).vendoredfeature: always build the bundled C source, even whenpkg-configis enabled, so a dependent crate can force a static build (https://github.com/gyscos/zstd-rs/pull/309).
Fixed
- The wasm shim's
qsortandmemcmpare nowunsafe(https://github.com/gyscos/zstd-rs/pull/367). - The x86-64 assembly is only built for x86_64 targets. It could never contribute code elsewhere - upstream gates it on
__x86_64__- and some toolchains, wasm ones especially, cannot process.Sinput at all (https://github.com/gyscos/zstd-rs/pull/368). -fvisibility=hiddenis no longer passed to MSVC (https://github.com/gyscos/zstd-rs/issues/352), and that is now decided from the target rather than the host, so cross-compiling to a*-windows-msvctarget works too.
- The
- v0.13.02023-10-11 · 2023-10-11 15:46 UTC
查看更新说明
- Bump zstd-safe to 7.0.0.
- Fix potential buffer overflow (https://github.com/gyscos/zstd-rs/pull/231).
- Some doc improvement.
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
zstd
This library is a rust binding for the zstd compression library.
Documentation
1 - Add to cargo.toml
$ cargo add zstd
# Cargo.toml
[dependencies]
zstd = "0.14"
2 - Usage
This library provides Read and Write wrappers to handle (de)compression,
along with convenience functions to made common tasks easier.
For instance, stream::copy_encode and stream::copy_decode are easy-to-use
wrappers around std::io::copy. Check the stream example:
use std::io;
// This function use the convenient `copy_encode` method
fn compress(level: i32) {
zstd::stream::copy_encode(io::stdin(), io::stdout(), level).unwrap();
}
// This function does the same thing, directly using an `Encoder`:
fn compress_manually(level: i32) {
let mut encoder = zstd::stream::Encoder::new(io::stdout(), level).unwrap();
io::copy(&mut io::stdin(), &mut encoder).unwrap();
encoder.finish().unwrap();
}
fn decompress() {
zstd::stream::copy_decode(io::stdin(), io::stdout()).unwrap();
}
Asynchronous support
The async-compression crate
provides an async-ready integration of various compression algorithms,
including zstd-rs.
Performance
Which function to use
The frame header can record how big the data will be once decompressed, and whoever reads it can then size their buffer in one go instead of growing it - worth roughly a factor of two to them. Only a compressor that knows the length up front can record it:
| you are compressing | into a Vec | into a Write |
|---|---|---|
a slice or a Vec | bulk::compress | compress_sized_into |
| a file | compress_file | compress_file_into |
a Read whose length you know | compress_sized | compress_sized_into |
any other Read | encode_all | copy_encode |
Everything but the last row records the size, whatever it is. The last row
records it when the source turns out to be shorter than 128 KiB, and leaves it
out beyond that. Reach for the Write column
when the data is large enough that you would rather not hold a Vec of it -
these stream straight through, so only the compressor's own buffers are in
memory. For a source that is neither a file nor a slice, its length is the
one thing you have to supply yourself.
On the way back, decode_all reads that header and sizes its output for you,
and decompressed_size gives you the same number for a buffer of your own.
Matching the C library
This wrapper adds little over the C library. On the same data at level 3, a
re-used bulk::Compressor and bulk::Decompressor land within a few percent
of what zstd -b3 reports for the same file. If you are seeing much less than
that, it is usually one of these:
- A debug build. Always measure with
--release; a debug build times this crate's wrappers rather than zstd. - Growing a
Vecrather than sizing it. The convenience functions that return aVecstart from an empty one. On decompression, sizing the output up front -bulk::decompress, orcopy_decodeinto aVec::with_capacity- was worth roughly a factor of two in our measurements. - Streaming data that is already in memory. For a slice you already hold,
the one-shot
bulkAPI avoids a copy through the streaming buffers. It is worth about ten percent on decompression, so it is a much smaller effect than sizing the output - theRead/Writewrappers are not the problem people usually assume they are. - Leaving compression single-threaded. With the
zstdmtfeature and several workers, compression of a 33 MB input went about three times faster here. It is a loss on small inputs, where there is not enough data to keep the workers busy.
To measure all of these on your own data:
cargo run --release --features zstdmt --example throughput -- <file>
It prints the matching zstd -b<level> command so you can compare against the
C library directly.
Cargo features
Enabled by default: legacy, arrays, zdict_builder.
| Feature | Default | Description |
|---|---|---|
arrays | yes | Use fixed-size arrays ([u8; N]) as output buffers. |
legacy | yes | Decode frames written by zstd versions older than 0.8. |
zdict_builder | yes | Train new dictionaries. Using a dictionary always works. |
experimental | Expose zstd's experimental API. It has no stability guarantees, and may change between zstd releases. | |
zstdmt | Multi-threaded compression inside the C library. | |
thin | Build a smaller C library, at some cost in speed and error reporting. | |
debug | Enable zstd's debug logs. | |
no_asm | Do not build the x86-64 assembly. | |
fat-lto, thin-lto | Cross-language LTO. Only works if clang builds the C library. | |
doc-cfg | Mark feature-gated items in the generated documentation. Needs a nightly compiler. | |
wasm | Does nothing; kept so that existing dependants keep building. |
The following change how the C library is obtained or built:
| Feature | Default | Description |
|---|---|---|
bindgen | Generate the bindings at build time rather than using the pre-generated ones. Needs libclang. | |
pkg-config | Link against a system-installed libzstd instead of building the bundled source. | |
vendored | Always build the bundled source, even when pkg-config is also enabled. Useful to force a static build from a dependent crate. | |
cmake | Build the C library with zstd's own CMake files instead of the cc crate, which handles cross-compilation better. Needs cmake. | |
rust-backend | Use libzstd-rs-sys, a Rust port of zstd, instead of the C library. Experimental, see below. |
The zstd-sys readme also covers the build-time environment variables, and what to do if the linker complains about a hidden zstd symbol being "referenced by DSO".
Compile it yourself
zstd is included as a submodule. To get everything during your clone, use:
git clone https://github.com/gyscos/zstd-rs --recursive
Or, if you cloned it without the --recursive flag,
call this from inside the repository:
git submodule update --init
Then, running cargo build should take care
of building the C library and linking to it.
Using Rust's global allocator
By default, zstd allocates its internal state (compression windows, match
finder tables, stream buffers, dictionaries) with the C runtime's
malloc/free, invisible to a custom #[global_allocator].
The with-rust-allocator feature makes every context and dictionary this
crate creates (Encoder, Decoder, bulk::Compressor, dictionaries, ...)
allocate through Rust's global allocator instead. It is crate-wide: enabling
it in your own Cargo.toml also covers zstd usage inside your
dependencies, since Cargo unifies features.
zstd = { version = "0.14", features = ["with-rust-allocator"] }
The feature implies experimental on zstd-safe because it relies on zstd's
ZSTD_create*_advanced() constructors, which require static linking. It
applies to contexts and prepared dictionaries created by the Rust wrappers,
including clones. Direct calls to zstd_sys, dictionary training, legacy
frame decoding, objects from the seekable format, and explicit zstd thread
pools can still allocate through C.
Prepared compression dictionaries use fixed parameters derived from their compression level and dictionary size. Unlike the default C constructor, the custom-allocator constructor does not store the level for later parameter adjustments based on the input size. Compressed output remains interoperable, but enabling this feature can change compression ratio, speed, and memory usage for prepared dictionaries, especially with large inputs and small dictionaries.
Using the Rust port of zstd
The rust-backend feature replaces the C library with libzstd-rs-sys, the
Trifecta Tech Foundation's port of zstd to Rust. Nothing else changes: the port
exports the same ZSTD_* functions that zstd-sys binds to, and this crate
calls them the same way.
zstd = { version = "0.14", features = ["rust-backend"] }
Like the port itself, still a 0.0.1 prerelease, this is experimental:
- The
seekableformat is not ported. Enabling both features is a compile error. - The features that configure the C build (
thin,debug,no_asm,fat-lto,thin-lto,cmake,pkg-configandvendored) have no effect, and neither doesZSTD_SYS_USE_PKG_CONFIG. - Multithreading and legacy decoding are always built into the port:
zstdmtonly controls whether this crate exposes the multithreading API. The port decodes legacy frames from zstd 0.5 to 0.7, whether or notlegacyis enabled, but not older ones. - The port allocates through Rust's global allocator, as
with-rust-allocatordoes for the C library. - It still needs a C compiler on x86-64 Unix, where the port assembles one
file, and it does not build for
wasm32-unknown-unknown. - It needs Rust 1.89 or newer.
- It is slower than the C library in most benchmarks: the pull request that
added it has a chart.
bench/backends.shruns the same comparison on your machine.
Build-time bindgen
The bindgen feature requires Rust 1.71 or newer and libclang.
The pre-generated bindings remain compatible with Rust 1.64.
This library includes a pre-generated bindings.rs file.
You can also generate new bindings at build-time, using the bindgen feature:
cargo build --features bindgen
TODO
- Benchmarks, optimizations, ...
Disclaimer
This implementation is largely inspired by bozaro's lz4-rs.
License
- The zstd C library is under a dual BSD/GPLv2 license. Its decoders for
legacy formats (the
legacyfeature, on by default) also include code under the BSD-2-Clause license. - This zstd-rs binding library is under a BSD-3-Clause license.