Hi @ewels — small observation surfaced while working through a bioconda packaging issue for TrimGalore (bioconda-recipes#65446 → #65450). Posting here so it's tracked; no urgency.
Observation
fastqc-rust v1.0.1's Cargo.toml opts out of flate2's default features for the direct dependency:
flate2 = { version = "1", default-features = false, features = ["rust_backend"] } # line 38
But then re-enables the C zlib backend through the crate's own default feature:
[features]
default = ["native-zlib"] # line 54
native-zlib = ["flate2/zlib"] # line 55
So downstream users who set default-features = false on fastqc-rust and on flate2 directly still end up with libz-sys linked into their binary, because Cargo's feature unification across the dependency graph adds flate2/zlib back through this path. There's also a secondary contributor — zip = "2" (line 41) is pulled with default features, and zip's default _deflate-any chain enables flate2's deflate-flate2 feature, which itself activates the C zlib backend.
Concrete example — TrimGalore v2.2.0
TrimGalore's Cargo.toml:
flate2 = { version = "1.1", default-features = false, features = ["zlib-rs"] }
Explicitly asks for the pure-Rust zlib-rs backend. But cargo tree -i libz-sys shows:
libz-sys v1.1.28
└── flate2 v1.1.9
├── fastqc-rust v1.0.1
├── gzp v2.0.2
├── hdf5-pure v0.1.1
└── noodles-bgzf v0.35.0
And cargo tree --edges features traces libz-sys activation via zip → deflate-flate2 → flate2/zlib (from fastqc-rust's native-zlib feature). The resulting Linux release binary dynamically links libz.so.1, which surfaced in bioconda as a runtime dependency that needed to be declared in the recipe's run: block.
Why this matters
Two downstream stories suffer:
- Static-binary / single-static-binary goals. TrimGalore's v2.x value prop is "no external runtime dependencies — single Rust binary". The
libz.so.1 dynamic link contradicts that.
- Packaging on minimal sysroots. Distros / containers that don't ship system zlib (or version-skew on libz) hit runtime errors.
There's no functional bug here — the C zlib backend works fine. It's a purity / packaging-ergonomics request.
Suggested directions (not prescriptive)
I'd lean toward making the pure-Rust path the default and the C-zlib path an explicit opt-in:
Option A — flip the default:
[features]
default = [] # was: default = ["native-zlib"]
native-zlib = ["flate2/zlib"] # unchanged — explicit opt-in for users who want C zlib speed
Option B — also fix zip's transitive default:
zip = { version = "2", default-features = false, features = ["deflate-zlib-rs"] } # or "deflate-miniz" for fully Rust path
Option A alone moves the explicit default but leaves zip's transitive pull. Option A + B together would deliver a fully pure-Rust path when downstream users disable native-zlib.
The behavior change for existing users is non-zero: anyone relying on the implicit C-zlib default would see a perf delta in deflate paths. Whether that's worth flipping depends on how much you weight static-binary stories vs. raw deflate speed for the typical FastQC use case (single file pass, deflate is rarely the bottleneck).
Coordination note
If you do ship a v1.0.2 with this change, TrimGalore would deliberately bump and re-verify output byte-identity against Java FastQC 0.12.1 (per our exact-pin convention). Happy to help drive a TrimGalore-side patch the moment a v1.0.2 lands. No urgency — happy to wait on your roadmap.
Thanks for the great crate either way.
Hi @ewels — small observation surfaced while working through a bioconda packaging issue for TrimGalore (bioconda-recipes#65446 → #65450). Posting here so it's tracked; no urgency.
Observation
fastqc-rustv1.0.1'sCargo.tomlopts out offlate2's default features for the direct dependency:But then re-enables the C zlib backend through the crate's own
defaultfeature:So downstream users who set
default-features = falseonfastqc-rustand onflate2directly still end up withlibz-syslinked into their binary, because Cargo's feature unification across the dependency graph addsflate2/zlibback through this path. There's also a secondary contributor —zip = "2"(line 41) is pulled with default features, andzip's default_deflate-anychain enablesflate2'sdeflate-flate2feature, which itself activates the C zlib backend.Concrete example — TrimGalore v2.2.0
TrimGalore's
Cargo.toml:Explicitly asks for the pure-Rust
zlib-rsbackend. Butcargo tree -i libz-sysshows:And
cargo tree --edges featurestraceslibz-sysactivation viazip → deflate-flate2 → flate2/zlib(fromfastqc-rust'snative-zlibfeature). The resulting Linux release binary dynamically linkslibz.so.1, which surfaced in bioconda as a runtime dependency that needed to be declared in the recipe'srun:block.Why this matters
Two downstream stories suffer:
libz.so.1dynamic link contradicts that.There's no functional bug here — the C zlib backend works fine. It's a purity / packaging-ergonomics request.
Suggested directions (not prescriptive)
I'd lean toward making the pure-Rust path the default and the C-zlib path an explicit opt-in:
Option A — flip the default:
Option B — also fix zip's transitive default:
Option A alone moves the explicit default but leaves zip's transitive pull. Option A + B together would deliver a fully pure-Rust path when downstream users disable
native-zlib.The behavior change for existing users is non-zero: anyone relying on the implicit C-zlib default would see a perf delta in deflate paths. Whether that's worth flipping depends on how much you weight static-binary stories vs. raw deflate speed for the typical FastQC use case (single file pass, deflate is rarely the bottleneck).
Coordination note
If you do ship a v1.0.2 with this change, TrimGalore would deliberately bump and re-verify output byte-identity against Java FastQC 0.12.1 (per our exact-pin convention). Happy to help drive a TrimGalore-side patch the moment a v1.0.2 lands. No urgency — happy to wait on your roadmap.
Thanks for the great crate either way.