Move buildSeqStore's preloaded external-sequence, LDM, external-producer, fallback, and ordinary matchfinder branch ordering into Rust. Keep the private CCtx, matchfinder, LDM, and producer operations behind narrow C callbacks, add ABI layout assertions, update the Rust boundary documentation, and cover each policy branch with focused tests. Test Plan: - cargo fmt --manifest-path rust/Cargo.toml -- --check - ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml --lib (690 passed) - ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - ulimit -v 41943040 && MAKEFLAGS=-j1 make -B -C programs -j1 zstd - ulimit -v 41943040 && MAKEFLAGS=-j1 make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s (84 tests and both short fuzz rounds passed)
15 KiB
Rust rewrite
This directory contains the in-progress Rust replacement for the zstd library and command-line program. During the migration, the crate is built as a static library and linked into the original C test programs. Production C translation units become declaration-only shims as their implementations move to Rust; the original C tests remain unchanged and provide compatibility coverage.
Component map
The crate is organized from low-level representation helpers toward the public zstd ABI:
- Common primitives
mem,bits,bitstream, andcpuimplement byte-order, bitstream, and target-feature operations used by the codecs.errors,debug,xxhash, andzstd_commonprovide common exported ABI functions and state.commoncontains shared frame constants and internal data types.
- Entropy coding
entropy_commonreads FSE normalized counts and Huffman statistics.fse_decompressbuilds FSE decoding tables and decodes FSE streams.fse_compressnormalizes counts, writes FSE headers, builds compression tables, and encodes FSE streams.huf_compressbuilds Huffman compression tables, writes table headers, and encodes one- and four-stream Huffman payloads.huf_decompressbuilds Huffman decoding tables and decodes X1 and X2 Huffman streams.
- Compression primitives
histcounts byte frequencies for FSE and Huffman compression.zstd_presplitchooses split points for full compression blocks.zstd_compress_literalsemits raw, RLE, and Huffman literal sections while preserving the compressor's Huffman-table repeat state.zstd_compress_statsconverts stored sequences into symbol codes, selects each block's symbol encoding types, compresses a seqStore's literals and sequences into a compressed-block body, builds the block entropy statistics shared with the superblock writer and the block splitter, and exports collected sequences in the publicZSTD_Sequenceformat. Its C shims extract the sequence store, the entropy-table leaves, and the twoZSTD_CCtx_paramsscalars these paths read.zstd_compress_block_splitsearches for profitable sequence-store partitions, whilezstd_compressemits those partitions through the Rust single-block serializer. C retains split discovery's context setup and the outer block-dispatch decision.zstd_compressalso owns ordinary sequence-block entropy emission, sequence collection, the legacy RLE compatibility gate, sequence-store construction, and its branch/fallback policy; C supplies only the private matchfinder, LDM block-preparation/consumption, external-sequence-producer, and state-preparation callbacks. Rust also owns the frame-chunk block loop, including block sizing, target/split/internal dispatch, output accounting, and frame-state updates; C supplies the private block-compression callbacks. Rust also owns the single-threaded buffered/stable stream state machine, including direct versus buffered output, pending-output draining, and frame reset policy. The external-sequence-and-literals block loop and public sequence conversion are Rust-owned as well; C retains only CCtx-facing adapters.zstd_compress_frameserializes frame headers, skippable frames, and the last empty block; it takes scalar frame parameters so the C-ownedZSTD_CCtx_paramslayout never crosses the language boundary.zstd_compressalso owns the transparent single-threaded and multithreaded stream-initialization policy: dictionary selection, parameter resolution, initial buffer sizing, and the ordered setup decisions are projected into Rust while C retains the private contexts and mutation callbacks. The public sequence APIs likewise use Rust-owned validation, frame-header, checksum, and output-accounting orchestration around C-owned block state. The public end-of-frame path and theZSTD_compress2_cfallback also use Rust-owned orchestration boundaries while C retains the context reset, stream adapter, checksum/epilogue, and trace callbacks.zstd_compress_paramsowns the compression-level tables (formerlyclevels.h), parameter bounds, clamping, validation, table selection, source/dictionary adjustment, and match-state/CDict size estimation. The C integration layer keeps the publicZSTD_*symbols and feeds the leaves configuration-owned scalars: the excluded-block-compressor strategy cascade, struct sizes, and sanitizer redzone policy.zstd_fastandzstd_double_fastimplement the single- and two-table fast block match finders, including attached and external dictionary paths.zstd_lazyimplements greedy, lazy, lazy2, and binary-tree matching, including row-based and dictionary search variants.zstd_opt_treemaintains the binary-tree index used by optimal matching;zstd_optowns the dynamic-programming price model, optimal parse, and sequence emission.zstd_ldmimplements long-distance-match parameter selection, table maintenance, sequence generation, sequence consumption, and sequence-store encoding.
- Dictionary building
divsufsortconstructs the suffix array that drives the legacyZDICTtrainer (ZDICT_trainFromBuffer_legacy);dict_builder_zdict,dict_builder_cover, anddict_builder_fastcoverown the sample analysis, training, and dictionary assembly. The corresponding C translation units are declaration-only ABI shims.
- Runtime support
threadingprovides platform pthread wrappers required by zstd headers.poolimplements the bounded worker pool used by multithreaded compression.- The
zstdmt_compressintegration keeps job descriptors and synchronization private to C while Rust owns input-retention scans, reusable input-range overlap decisions, outer scheduling and end-directive adjustments, job-creation decisions, compression-job stage sequencing and error flow, and pending-output decisions through scalar job projections.
- Dictionary support
zstd_ddictowns, loads, copies, and references decode dictionaries.
- Legacy decoding
legacyhosts one frozen module per historical format;legacy::zstd_v01throughlegacy::zstd_v07port the seven self-contained historical decoders. Their original C translation units remain declaration-only shims for the native build.
- Block decompression
zstd_decompress_blockdecodes literal and sequence sections, maintains FSE/Huffman repeat state, executes compressed-block sequences, and selects the short or long sequence decoder from projected configuration and history state. C retains the decoder-context layout and configuration projection; the public and fullbench block-decoder wrappers are Rust-owned.zstd_decompressowns the public decompression context, one-shot, dictionary, parameter, and streaming state machines. Its C shim retains the configuration-dependent context layout and platform details, plus legacy and trace leaves; Rust owns decoder storage allocation and custom memory dispatch.
- Command-line frontend
zstd_cliowns the Rust parser, safety policy, and dispatch. It is built by the separatecli/static-library package only for program archives, so library builds do not acquire program-only dependencies. The Cfileiolayer retains the format-specific codec callbacks, private asynchronous-pool adapters, metadata, zstd codec/error mapping, and adaptive-policy integration, while Rust owns the mixed-format probe/dispatch loop, zstd stream-compression I/O loop, scalar adaptive decisions, optional-format decompression loops, decompression result policy/final accounting, both shared- and separate-destination multi-file compression schedulers, and both shared- and separate-destination decompression schedulers. C retains destination-name construction and the private file/resource/format callbacks for these scheduler boundaries. Rust already owns the file preference policy, filename decisions, source/destination opening, dictionary buffers, asynchronous I/O pools, and pass-through copy leaf.timefnprovides the monotonic nanosecond clock behindUTIL_time_t, whilebenchfnowns the benchmark run/timing loop (BMK_benchFunction,BMK_benchTimedFn) andbenchzstdowns benchmark orchestration and reporting. Both live in Rust; the C translation units are ABI shims. C test binaries (fullbench, fuzzer, zstreamtest, paramgrill, ...) link a helpers-only build of the archive, produced without the package'sclifeature, because the parser layer requires the Cfileiobackend that tests do not compile.
Dictionary-ingestion dispatch, CCtx dictionary/prefix attachment dispatch, the CLI zstd compression stream loop, scalar adaptive decisions, and the shared- and separate-destination multi-file compression schedulers, shared- and separate-destination decompression scheduling, single-threaded stream initialization and the buffered/stable stream state machine, MT stream initialization, MT outer scheduling and flush policy, MT compression-job stage sequencing and error flow, public sequence-API orchestration, sequence-store and block policy, external-producer invocation and success-path validation, external-sequence-store reset, external-sequence/literals block loop, optional-format decompression loops, block decoder wrappers, decompression result policy, and public end-of-frame compression orchestration, public CCtx-copy pledge/frame-parameter policy, public CCtx allocation validation and allocate-before-init ordering, public static-CCtx workspace validation and initialization dispatch, and public CDict constructor parameter selection, default-level normalization, and workspace/object teardown ordering, public static-CDict workspace validation and initialization dispatch, public advanced-CDict one-shot validation and begin/end ordering, advanced-CDict parameter selection, dedicated-search fallback, row-matchfinder resolution, and create/init/failure ordering, public advanced-compression parameter validation/init policy, public usingDict parameter selection, dictionary-presence handling, and default-level normalization, and public usingCDict frame-policy construction and begin/end sequencing, legacy public CDict-begin frame-policy construction and unknown-source pledge, and the compressBegin_usingDict family’s unknown-source parameter selection and default-level normalization, and public advanced-begin parameter validation and init-then-begin ordering now run in Rust. CDict advanced private workspace construction, private static-CCtx and static-CDict workspace construction and dictionary-content allocation/loading, and advanced-CDict dictionary-content loading remain in C. Rust now owns advanced-CDict custom-memory validation, workspace-size query/allocation, allocation/create/init cleanup ordering, the CCtx workspace-size formula, the scalar CCtx-reset plan, post-match-state storage reservation order, and match-state reset policy/order; C retains private layout-size inputs, workspace resize/layout, private field publication, and allocator callbacks. Private CCtx reset/matchfinder/workspace operations and codec/adaptive-policy callbacks remain in C. CDict initialization ordering and scalar publication, shared compression-begin dictionary selection, CDict reset attach-versus-copy selection, and CDict-begin parameter selection, initialization ordering, and source-window policy now run in Rust behind private-state bridges; their C callbacks and final begin/reset/attach/copy operations remain. The remaining C paths must move before the rewrite is complete. Keeping that boundary explicit prevents a passing hybrid build from being mistaken for the final all-Rust result.
Legacy decoding
Each lib/legacy/zstd_v0N.c file is a frozen snapshot of the entropy coders
and frame logic of one historical release. The Rust ports in src/legacy/
keep that property: every version owns its own frozen FSE/Huff0 and frame
logic, ported line by line, and must never reuse the modern entropy modules
or share code with other legacy versions. Outputs and error codes must be
byte-identical to the original C files. Their only shared dependency is the
errors module, matching the C files' error_private.h include.
Cargo features legacy-v01 .. legacy-v07 gate the per-version modules and
are never default features. All seven modules are now available; the build
systems derive the enabled feature list from the C configuration:
lib/Makefileandprograms/MakefilemapZSTD_LEGACY_SUPPORT=Nto the features for versions >= N (0 disables legacy), matching theZSTD_LEGACY_FILESselection inlib/libzstd.mk.tests/Makefilealways enables all seven features because the test objects compile everylib/legacy/*.cfile regardless of dispatch level.build/mesonmapslegacy_levellike the makefiles;build/cmakeenables all seven wheneverZSTD_LEGACY_SUPPORTis on because it always compiles all seven C files.
Every build system also encodes the legacy selection in the Rust target
directory name (for example c1-d1-default-legacy5), for the same reason the
HUF mode is encoded there: a cached archive built for one configuration must
never be linked into a build that expects another.
Each port adds src/legacy/zstd_v0N.rs, registers it in src/legacy/mod.rs
behind its feature, and reduces lib/legacy/zstd_v0N.c to a
declaration-only shim. For v0.1 the streaming ZSTDv01_Dctx state lives
entirely in Rust: C code only ever holds an opaque pointer, so the C-side
struct definition is gone.
Compatibility boundary
The public ABI continues to come from the existing headers under lib/.
Exported Rust functions therefore use C layout and calling conventions. A C
source file whose implementation has moved to Rust remains in the original
makefile source list as a small shim so header configuration and platform
preprocessor behavior stay available during the transition.
The library, test, and program makefiles select an archive directory for the active C configuration: enabled compression/decompression/dictionary-builder modules, default or forced HUF X1/X2, and the matching Rust target for 32-bit C binaries. The native static archive flattens Rust object members rather than nesting a Rust archive, while the native shared library retains all migrated Rust exports. When the HUF mode changes, the test and program paths also rebuild cached C outputs before linking. This prevents original C tests from using a stale or configuration-incompatible implementation.
Validation
Run focused Rust checks from this directory:
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test --all-targets
cargo build --release
The program-only Rust archive has its own feature matrix and should be checked
from rust/cli as well:
cargo clippy --all-targets -- -D warnings
cargo test --all-targets
cargo test --no-default-features --features cli,compression --all-targets
cargo test --no-default-features --features cli,decompression --all-targets
cargo test --no-default-features --all-targets
Then run original compatibility tests from the repository root, starting with the narrow target for the component being migrated. For example:
make -C tests fuzzer
./tests/fuzzer -i1 --no-big-tests
make -C tests test-rust-lib-smoke
Broader tests/Makefile targets remain the authoritative integration gates as
more of the library and CLI are rewritten.