This library is a rust binding for the zstd compression library.
$ cargo add zstd# Cargo.toml
[dependencies]
zstd = "0.14"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();
}The async-compression crate
provides an async-ready integration of various compression algorithms,
including zstd-rs.
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 (no size) |
copy_encode (no size) |
Everything but the last row records the size. 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.
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.
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. |
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".
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.
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.
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
- Benchmarks, optimizations, ...
This implementation is largely inspired by bozaro's lz4-rs.
- The zstd C library is under a dual BSD/GPLv2 license.
- This zstd-rs binding library is under a BSD-3-Clause license.