Files
zstd-rs/rust
ddidderr c4e034a987 feat(cli): expand file lists, output dirs, and --list in Rust frontend
The Rust CLI frontend rejected -r, --filelist, --output-dir-flat,
--output-dir-mirror, -l and --list, which tests/playTests.sh exercises
heavily (32 recursive runs, 20 file-list runs, 32 output-dir runs, 29
list runs). These are pure parser/dispatch features: the actual
directory traversal, file-of-names reading, and frame inspection remain
in C.

Mirror the pre-migration C zstdcli.c control flow:

- --filelist names are collected during parsing and expanded after the
  symbolic-link filter through UTIL_createFileNamesTable_fromFileName,
  so list contents are never symlink-filtered and an unreadable,
  irregular, empty, or over-long list aborts with "error reading NAME"
  and exit code 1, exactly like the C loop.
- -r expands the collected inputs through UTIL_createExpandedFNT,
  following links only under --force (the C followLinks flag). The
  input-name count is saved before expansion: when expansion leaves no
  files but names were given (only empty directories), the run prints
  "please provide correct input file(s) or non-empty directories --
  ignored" and exits 0 instead of falling back to stdin. -r parses only
  on unix/windows targets, mirroring UTIL_HAS_CREATEFILELIST.
- --output-dir-flat / --output-dir-mirror pass through to the existing
  output-dir arguments of FIO_{,de}compressMultipleFilenames (test mode
  included). Both use exact NEXT_FIELD semantics via a new next_field
  helper: an attached empty value (--output-dir-flat=) stays empty and
  is rejected with the C "output dir cannot be empty string" message
  instead of consuming the next argument. --output-dir-mirror parses
  only on POSIX targets, mirroring UTIL_HAS_MIRRORFILELIST.
- -l/--list dispatches to FIO_listMultipleFiles after expansion and
  before the console checks, so "No files given", the stdin refusal,
  display-level gating, and per-file exit codes are the C code paths.
  Builds without the decompression feature report "file information is
  not supported" like ZSTD_NODECOMPRESS builds.
- g_utilDisplayLevel is published before any expansion so traversal
  warnings honor -q/-v, and benchmark dispatch moved after expansion so
  -rqi0b1e2 style aggregates benchmark expanded directories like C.

FIO_setNbFilesTotal keeps receiving the post-expansion count because
apply_preferences reads cli.inputs after all expansion steps.

The FileNamesTable struct is mirrored in Rust (pointer/pointer/size/
size); tables returned by the UTIL helpers are drained into owned
CStrings and freed immediately, so no C buffer lifetime escapes.

Test Plan:
- cd rust/cli && cargo clippy --all-targets -- -D warnings && cargo
  test --all-targets, plus the compression-only and decompression-only
  feature matrices; new parser tests cover -r aggregation, both
  --filelist syntaxes, option-as-argument rejection, empty output-dir
  rejection, --list forms, and --list=x rejection.
- make -C programs zstd, then diffed behavior against a pre-migration
  C binary (f8745da6): -r on a nested tree produces identical file
  sets and identical -o output; --filelist with one and two lists,
  missing list ("zstd: error reading X", exit 1), and binary-garbage
  list (exit 1); --output-dir-flat compress + decompress round-trip;
  --output-dir-mirror round-trip and '..' rejection; -l/-lv identical
  stdout, identical exit codes for no-files, non-zst, and stdin cases;
  -r on an empty directory exits 0 with the same message; -c -r with
  stdin fallback produces identical frames.
2026-07-12 09:02:21 +02:00
..

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, and cpu implement byte-order, bitstream, and target-feature operations used by the codecs.
    • errors, debug, xxhash, and zstd_common provide common exported ABI functions and state.
    • common contains shared frame constants and internal data types.
  • Entropy coding
    • entropy_common reads FSE normalized counts and Huffman statistics.
    • fse_decompress builds FSE decoding tables and decodes FSE streams.
    • fse_compress normalizes counts, writes FSE headers, builds compression tables, and encodes FSE streams.
    • huf_compress builds Huffman compression tables, writes table headers, and encodes one- and four-stream Huffman payloads.
    • huf_decompress builds Huffman decoding tables and decodes X1 and X2 Huffman streams.
  • Compression primitives
    • hist counts byte frequencies for FSE and Huffman compression.
    • zstd_presplit chooses split points for full compression blocks.
    • zstd_compress_literals emits raw, RLE, and Huffman literal sections while preserving the compressor's Huffman-table repeat state.
    • zstd_compress_frame serializes frame headers, skippable frames, and the last empty block; it takes scalar frame parameters so the C-owned ZSTD_CCtx_params layout never crosses the language boundary.
    • zstd_compress_params owns the compression-level tables (formerly clevels.h), parameter bounds, clamping, validation, table selection, source/dictionary adjustment, and match-state/CDict size estimation. The C integration layer keeps the public ZSTD_* symbols and feeds the leaves configuration-owned scalars: the excluded-block-compressor strategy cascade, struct sizes, and sanitizer redzone policy.
    • zstd_fast and zstd_double_fast implement the single- and two-table fast block match finders, including attached and external dictionary paths.
    • zstd_lazy implements greedy, lazy, lazy2, and binary-tree matching, including row-based and dictionary search variants.
    • zstd_opt_tree maintains the binary-tree index used by optimal matching; the dynamic-programming optimal parser itself remains in C for now.
    • zstd_ldm implements long-distance-match parameter selection, table maintenance, sequence generation, and sequence consumption.
  • Dictionary building
    • divsufsort constructs the suffix array that drives the legacy ZDICT trainer (ZDICT_trainFromBuffer_legacy). The sample analysis and dictionary assembly in zdict.c, cover.c, and fastcover.c remain C.
  • Runtime support
    • threading provides platform pthread wrappers required by zstd headers.
    • pool implements the bounded worker pool used by multithreaded compression.
  • Dictionary support
    • zstd_ddict owns, loads, copies, and references decode dictionaries.
  • Block decompression
    • zstd_decompress_block decodes literal and sequence sections, maintains FSE/Huffman repeat state, and executes compressed-block sequences.
    • zstd_decompress owns the public decompression context, one-shot, dictionary, parameter, and streaming state machines. Its C shim retains configuration-dependent context allocation plus legacy and trace leaves.
  • Command-line frontend
    • zstd_cli owns the Rust parser, safety policy, and dispatch. It is built by the separate cli/ static-library package only for program archives, so library builds do not acquire program-only dependencies. The C fileio backend still owns file opening, safe replacement, sparse writes, metadata, and streaming I/O.
    • timefn provides the monotonic nanosecond clock behind UTIL_time_t, and benchfn owns the benchmark run/timing loop (BMK_benchFunction, BMK_benchTimedFn) used by the CLI benchmark mode and by C test tools. Both live in the cli/ package, but C test binaries (fullbench, fuzzer, zstreamtest, paramgrill, ...) link a helpers-only build of that archive, produced without the package's cli feature, because the parser layer requires the C fileio backend that tests do not compile. Benchmark orchestration and reporting (benchzstd.c) remain C, reached from the Rust parser through the ZSTD_NOBENCH-gated bridge in zstdcli.c.

The optimal block matcher, high-level frame compression, dictionary-building except suffix-array construction, legacy decoding callbacks, benchmark orchestration (benchzstd), and the CLI file-I/O backend are still C. They 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.

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.