Skip to content

Add a pure-Rust Zstandard backend behind a zstd-rust feature - #45

Open
polaz wants to merge 2 commits into
SOF3:masterfrom
polaz:zstd-rust-backend
Open

polaz wants to merge 2 commits into
SOF3:masterfrom
polaz:zstd-rust-backend

Conversation

@polaz

@polaz polaz commented Sep 26, 2026

Copy link
Copy Markdown

The zstd feature binds the C library through zstd-sys, so every crate that embeds data with zstd needs a C toolchain at build time and links C code. This adds an opt-in zstd-rust feature that provides the same CompressionMethod::Zstd and the same with zstd syntax through structured-zstd, a pure-Rust implementation of the format (no FFI, no cmake).

Nothing changes for existing users: the default features are untouched.

Behaviour

  • zstd-rust alone: Zstandard without any C dependency (cargo tree shows neither zstd, zstd-sys nor libflate).
  • zstd and zstd-rust together: zstd-rust is used. A crate that asks for it is opting out of the C toolchain, and a transitive default elsewhere in the graph should not override that.
  • The frame format is the same, so a build with one backend reads what the other embedded.

Changes

  • compress: the backend is selected by feature behind the existing FlateEncoder / FlateDecoder API. The pure-Rust coders keep their state inline, so they are boxed to keep the enums compact (clippy::large_enum_variant).
  • codegen, root crate: the feature is passed through; with zstd accepts either feature.
  • Tests: the existing suite runs under zstd-rust; a new compress/tests/zstd_interop.rs decodes C-zstd frames with the Rust backend and the reverse (built with both features).
  • CI: matrix entries for --no-default-features --features zstd-rust and --features zstd-rust, plus a check that zstd-rust pulls in no C bindings.
  • Docs: the # Algorithm section of flate! now describes both Zstandard backends.

Measurements

Decompressing jieba-rs's embedded dictionary (5,071,843 bytes), 200 decodes each, Apple M-series:

backend min median compressed size
zstd (C, level 0) 5.56 ms 6.08 ms 2,126,841 B
zstd-rust (CompressionLevel::Default) 6.98 ms 7.57 ms 2,122,975 B

End to end, the first Jieba::new in a process moves from 65.7 ms to 68.5 ms (min of 30 interleaved runs), about +4%; the first TfIdf::default from 33.1 ms to 34.5 ms. Opting in trades that for a build with no C toolchain.

Test plan

  • cargo test --workspace with default features, --no-default-features --features deflate, --no-default-features --features zstd, --no-default-features --features zstd-rust, and --features zstd-rust
  • cargo clippy --workspace --all-targets for the same sets (no new warnings)
  • cargo fmt --all -- --check
  • cargo tree --no-default-features --features zstd-rust --invert zstd-sys finds nothing

The zstd feature binds the C library through zstd-sys, which needs a C
toolchain at build time and links C code into every consumer. The new
zstd-rust feature provides the same CompressionMethod::Zstd and the same
`with zstd` syntax through structured-zstd, a pure-Rust implementation
of the format. Frames are interchangeable between the two backends; when
both features are enabled, zstd-rust is used.

- compress: backend selected by feature, same public API.
- codegen, root crate: feature passed through.
- Tests run under zstd-rust as well; a new interop test decodes C-zstd
  frames with the Rust backend and the reverse.
- CI: test matrix entries for zstd-rust, and a check that it pulls in
  neither zstd, zstd-sys nor libflate.
structured-zstd keeps the coder state inline, which made the Zstd variants of FlateEncoder and FlateDecoder far larger than the others (clippy::large_enum_variant). Boxing them keeps the enums compact; the C backend already holds its state behind a pointer.
Comment thread compress/src/lib.rs
type ZstdDecoder<R> = zstd::Decoder<'static, std::io::BufReader<R>>;

#[cfg(feature = "zstd-rust")]
fn zstd_encoder<W: Write>(write: W) -> io::Result<ZstdEncoder<W>> {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a fan of exclusive features. i think they should be exposed as if separate decoding formats rather than replacing one another.

@@ -0,0 +1,53 @@
// include-flate

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this have been a unit test instead of an integration test?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants