From 46df9753864eb3c001cb4bf54dada3c54533297f Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 14:15:29 +0800 Subject: [PATCH 01/16] refactor: gate sketches with feature flags Signed-off-by: tison --- datasketches/Cargo.toml | 10 ++++++++++ datasketches/src/lib.rs | 14 ++++++++++++++ datasketches/tests/bloom_serialization_test.rs | 2 ++ datasketches/tests/countmin_test.rs | 2 ++ datasketches/tests/cpc_serialization_test.rs | 2 ++ datasketches/tests/cpc_union_test.rs | 2 ++ datasketches/tests/cpc_update_test.rs | 2 ++ datasketches/tests/cpc_wrapper_test.rs | 2 ++ .../tests/frequencies_serialization_test.rs | 2 ++ datasketches/tests/frequencies_update_test.rs | 2 ++ datasketches/tests/hll_serialization_test.rs | 2 ++ datasketches/tests/hll_union_test.rs | 2 ++ datasketches/tests/hll_update_test.rs | 2 ++ datasketches/tests/tdigest_serialization_test.rs | 2 ++ datasketches/tests/tdigest_test.rs | 2 ++ datasketches/tests/theta_intersection_test.rs | 2 ++ datasketches/tests/theta_serialization_test.rs | 2 ++ datasketches/tests/theta_sketch_test.rs | 2 ++ 18 files changed, 56 insertions(+) diff --git a/datasketches/Cargo.toml b/datasketches/Cargo.toml index 214288e..ba79372 100644 --- a/datasketches/Cargo.toml +++ b/datasketches/Cargo.toml @@ -34,6 +34,16 @@ keywords = ["sketch", "hyperloglog", "probabilistic"] all-features = true rustdoc-args = ["--cfg", "docsrs"] +[features] +default = [] +bloom = [] +countmin = [] +cpc = [] +frequencies = [] +hll = [] +tdigest = [] +theta = [] + [dev-dependencies] googletest = { workspace = true } insta = { workspace = true } diff --git a/datasketches/src/lib.rs b/datasketches/src/lib.rs index 33ea2f8..0bd9d1f 100644 --- a/datasketches/src/lib.rs +++ b/datasketches/src/lib.rs @@ -30,16 +30,30 @@ #[cfg(target_endian = "big")] compile_error!("datasketches does not support big-endian targets"); +#[cfg(feature = "bloom")] +#[cfg_attr(docsrs, doc(cfg(feature = "bloom")))] pub mod bloom; pub mod codec; pub mod common; +#[cfg(feature = "countmin")] +#[cfg_attr(docsrs, doc(cfg(feature = "countmin")))] pub mod countmin; +#[cfg(feature = "cpc")] +#[cfg_attr(docsrs, doc(cfg(feature = "cpc")))] pub mod cpc; pub mod error; +#[cfg(feature = "frequencies")] +#[cfg_attr(docsrs, doc(cfg(feature = "frequencies")))] pub mod frequencies; pub mod hash_value; +#[cfg(feature = "hll")] +#[cfg_attr(docsrs, doc(cfg(feature = "hll")))] pub mod hll; +#[cfg(feature = "tdigest")] +#[cfg_attr(docsrs, doc(cfg(feature = "tdigest")))] pub mod tdigest; +#[cfg(feature = "theta")] +#[cfg_attr(docsrs, doc(cfg(feature = "theta")))] pub mod theta; mod hash; diff --git a/datasketches/tests/bloom_serialization_test.rs b/datasketches/tests/bloom_serialization_test.rs index 15daba2..f096679 100644 --- a/datasketches/tests/bloom_serialization_test.rs +++ b/datasketches/tests/bloom_serialization_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "bloom")] + mod common; use std::fs; diff --git a/datasketches/tests/countmin_test.rs b/datasketches/tests/countmin_test.rs index dddc72c..a8681e4 100644 --- a/datasketches/tests/countmin_test.rs +++ b/datasketches/tests/countmin_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "countmin")] + use datasketches::countmin::CountMinSketch; #[test] diff --git a/datasketches/tests/cpc_serialization_test.rs b/datasketches/tests/cpc_serialization_test.rs index bbcb56e..85b749a 100644 --- a/datasketches/tests/cpc_serialization_test.rs +++ b/datasketches/tests/cpc_serialization_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "cpc")] + mod common; use std::fs; diff --git a/datasketches/tests/cpc_union_test.rs b/datasketches/tests/cpc_union_test.rs index 36badbd..5bc3b18 100644 --- a/datasketches/tests/cpc_union_test.rs +++ b/datasketches/tests/cpc_union_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "cpc")] + use datasketches::cpc::CpcSketch; use datasketches::cpc::CpcUnion; use googletest::assert_that; diff --git a/datasketches/tests/cpc_update_test.rs b/datasketches/tests/cpc_update_test.rs index 7b814f7..8e78995 100644 --- a/datasketches/tests/cpc_update_test.rs +++ b/datasketches/tests/cpc_update_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "cpc")] + use datasketches::common::NumStdDev; use datasketches::cpc::CpcSketch; use googletest::assert_that; diff --git a/datasketches/tests/cpc_wrapper_test.rs b/datasketches/tests/cpc_wrapper_test.rs index 6355ed0..4545e20 100644 --- a/datasketches/tests/cpc_wrapper_test.rs +++ b/datasketches/tests/cpc_wrapper_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "cpc")] + use datasketches::common::NumStdDev; use datasketches::cpc::CpcSketch; use datasketches::cpc::CpcUnion; diff --git a/datasketches/tests/frequencies_serialization_test.rs b/datasketches/tests/frequencies_serialization_test.rs index f8d95bc..fd82761 100644 --- a/datasketches/tests/frequencies_serialization_test.rs +++ b/datasketches/tests/frequencies_serialization_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "frequencies")] + mod common; use std::fs; diff --git a/datasketches/tests/frequencies_update_test.rs b/datasketches/tests/frequencies_update_test.rs index a5a98e1..f15f219 100644 --- a/datasketches/tests/frequencies_update_test.rs +++ b/datasketches/tests/frequencies_update_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "frequencies")] + use datasketches::frequencies::ErrorType; use datasketches::frequencies::FrequentItemsSketch; diff --git a/datasketches/tests/hll_serialization_test.rs b/datasketches/tests/hll_serialization_test.rs index 4f7291b..c3f9d97 100644 --- a/datasketches/tests/hll_serialization_test.rs +++ b/datasketches/tests/hll_serialization_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "hll")] + mod common; use std::fs; diff --git a/datasketches/tests/hll_union_test.rs b/datasketches/tests/hll_union_test.rs index 91080bf..5bc3bb8 100644 --- a/datasketches/tests/hll_union_test.rs +++ b/datasketches/tests/hll_union_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "hll")] + //! HyperLogLog Union Integration Tests //! //! These tests verify the public API behavior of HllUnion, focusing on: diff --git a/datasketches/tests/hll_update_test.rs b/datasketches/tests/hll_update_test.rs index e72c6fc..0b0d611 100644 --- a/datasketches/tests/hll_update_test.rs +++ b/datasketches/tests/hll_update_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "hll")] + use datasketches::common::NumStdDev; use datasketches::hll::HllSketch; use datasketches::hll::HllType; diff --git a/datasketches/tests/tdigest_serialization_test.rs b/datasketches/tests/tdigest_serialization_test.rs index 18f58ce..d52983e 100644 --- a/datasketches/tests/tdigest_serialization_test.rs +++ b/datasketches/tests/tdigest_serialization_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "tdigest")] + mod common; use std::fs; diff --git a/datasketches/tests/tdigest_test.rs b/datasketches/tests/tdigest_test.rs index b61d779..cce2130 100644 --- a/datasketches/tests/tdigest_test.rs +++ b/datasketches/tests/tdigest_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "tdigest")] + use datasketches::tdigest::TDigestMut; use googletest::assert_that; use googletest::prelude::eq; diff --git a/datasketches/tests/theta_intersection_test.rs b/datasketches/tests/theta_intersection_test.rs index 4959529..9b98062 100644 --- a/datasketches/tests/theta_intersection_test.rs +++ b/datasketches/tests/theta_intersection_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "theta")] + use datasketches::theta::CompactThetaSketch; use datasketches::theta::ThetaIntersection; use datasketches::theta::ThetaSketch; diff --git a/datasketches/tests/theta_serialization_test.rs b/datasketches/tests/theta_serialization_test.rs index fd07d51..48c1c12 100644 --- a/datasketches/tests/theta_serialization_test.rs +++ b/datasketches/tests/theta_serialization_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "theta")] + mod common; use std::fs; diff --git a/datasketches/tests/theta_sketch_test.rs b/datasketches/tests/theta_sketch_test.rs index 28448c2..55664ef 100644 --- a/datasketches/tests/theta_sketch_test.rs +++ b/datasketches/tests/theta_sketch_test.rs @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +#![cfg(feature = "theta")] + use datasketches::common::NumStdDev; use datasketches::hash_value; use datasketches::theta::ThetaSketch; From 21bf744fbf4604777422c71f3ba49fe6d538477d Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 14:21:28 +0800 Subject: [PATCH 02/16] human run Signed-off-by: tison --- Cargo.toml | 1 - datasketches/Cargo.toml | 2 ++ datasketches/src/lib.rs | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 38d8511..a51f388 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,6 @@ datasketches = { path = "datasketches" } clap = { version = "4.5.20", features = ["derive"] } insta = { version = "1.46.1" } googletest = { version = "0.14.2" } -rand = { version = "0.9.2" } which = { version = "8.0.0" } [workspace.lints.rust] diff --git a/datasketches/Cargo.toml b/datasketches/Cargo.toml index ba79372..696fe39 100644 --- a/datasketches/Cargo.toml +++ b/datasketches/Cargo.toml @@ -36,6 +36,8 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = [] + +# Each sketch has its own feature, so that users can opt in to only the sketches they need. bloom = [] countmin = [] cpc = [] diff --git a/datasketches/src/lib.rs b/datasketches/src/lib.rs index 0bd9d1f..833d463 100644 --- a/datasketches/src/lib.rs +++ b/datasketches/src/lib.rs @@ -30,30 +30,30 @@ #[cfg(target_endian = "big")] compile_error!("datasketches does not support big-endian targets"); +// sketches modules + #[cfg(feature = "bloom")] -#[cfg_attr(docsrs, doc(cfg(feature = "bloom")))] pub mod bloom; -pub mod codec; -pub mod common; #[cfg(feature = "countmin")] -#[cfg_attr(docsrs, doc(cfg(feature = "countmin")))] pub mod countmin; #[cfg(feature = "cpc")] -#[cfg_attr(docsrs, doc(cfg(feature = "cpc")))] pub mod cpc; -pub mod error; #[cfg(feature = "frequencies")] -#[cfg_attr(docsrs, doc(cfg(feature = "frequencies")))] pub mod frequencies; -pub mod hash_value; #[cfg(feature = "hll")] -#[cfg_attr(docsrs, doc(cfg(feature = "hll")))] pub mod hll; #[cfg(feature = "tdigest")] -#[cfg_attr(docsrs, doc(cfg(feature = "tdigest")))] pub mod tdigest; #[cfg(feature = "theta")] -#[cfg_attr(docsrs, doc(cfg(feature = "theta")))] pub mod theta; +// common used modules + +pub mod codec; +pub mod common; +pub mod error; +pub mod hash_value; + +// private internal modules + mod hash; From 472a70b99e5c5d5708f9fe492a7451cb9f54993c Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 14:29:03 +0800 Subject: [PATCH 03/16] better test Signed-off-by: tison --- Cargo.lock | 58 +++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + xtask/Cargo.toml | 1 + xtask/src/main.rs | 21 ++++++++++++++++- 4 files changed, 80 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 3f3c50a..759784f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,6 +79,38 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8abf5d501fd757c2d2ee78d0cc40f606e92e3a63544420316565556ed28485e2" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -421,6 +453,10 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" @@ -429,6 +465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -500,6 +537,26 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -692,6 +749,7 @@ dependencies = [ name = "x" version = "0.0.0" dependencies = [ + "cargo_metadata", "clap", "which", ] diff --git a/Cargo.toml b/Cargo.toml index a51f388..a6e40dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ datasketches = { path = "datasketches" } clap = { version = "4.5.20", features = ["derive"] } insta = { version = "1.46.1" } googletest = { version = "0.14.2" } +cargo_metadata = { version = "0.23.1" } which = { version = "8.0.0" } [workspace.lints.rust] diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 5b04167..080aa8a 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -31,6 +31,7 @@ release = false [dependencies] clap = { workspace = true } +cargo_metadata = { workspace = true } which = { workspace = true } [lints] diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 6af7f65..44b6e25 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -51,10 +51,29 @@ struct CommandTest { impl CommandTest { fn run(self) { - run_command(make_test_cmd(self.no_capture, &[])); + let features = get_sketch_features(); + let features_refs: Vec<&str> = features.iter().map(|s| s.as_str()).collect(); + run_command(make_test_cmd(self.no_capture, &features_refs)); } } +fn get_sketch_features() -> Vec { + let metadata = cargo_metadata::MetadataCommand::new() + .manifest_path(std::path::Path::new(env!("CARGO_WORKSPACE_DIR")).join("Cargo.toml")) + .exec() + .expect("failed to get cargo metadata"); + let pkg = metadata + .workspace_packages() + .into_iter() + .find(|pkg| pkg.name == "datasketches") + .expect("failed to find datasketches package"); + pkg.features + .keys() + .filter(|&f| f != "default") + .cloned() + .collect() +} + #[derive(Parser)] #[clap(name = "lint")] struct CommandLint { From e7f43eea0ec71d2a233e1b71c017bde98d3b005d Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 14:35:59 +0800 Subject: [PATCH 04/16] fixup Signed-off-by: tison --- xtask/src/main.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 44b6e25..57a02b0 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::path::Path; use std::process::Command as StdCommand; use clap::Parser; @@ -51,27 +52,29 @@ struct CommandTest { impl CommandTest { fn run(self) { - let features = get_sketch_features(); + let features = sketches_features(); let features_refs: Vec<&str> = features.iter().map(|s| s.as_str()).collect(); run_command(make_test_cmd(self.no_capture, &features_refs)); } } -fn get_sketch_features() -> Vec { - let metadata = cargo_metadata::MetadataCommand::new() - .manifest_path(std::path::Path::new(env!("CARGO_WORKSPACE_DIR")).join("Cargo.toml")) +fn sketches_features() -> Vec { + use cargo_metadata::Metadata; + use cargo_metadata::MetadataCommand; + + let datasketches_manifest = Path::new(env!("CARGO_WORKSPACE_DIR")).join("Cargo.toml"); + + let Metadata { packages, .. } = MetadataCommand::new() + .manifest_path(datasketches_manifest) .exec() .expect("failed to get cargo metadata"); - let pkg = metadata - .workspace_packages() + + let pkg = packages .into_iter() - .find(|pkg| pkg.name == "datasketches") + .find(|p| p.name == "datasketches") .expect("failed to find datasketches package"); - pkg.features - .keys() - .filter(|&f| f != "default") - .cloned() - .collect() + + pkg.features.into_keys().collect() } #[derive(Parser)] From 71e929d4e531ef5e6c1b63d22b71195d3e79ac08 Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 14:40:02 +0800 Subject: [PATCH 05/16] fixup Signed-off-by: tison --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- xtask/src/main.rs | 5 ++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 759784f..a9c0fff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,18 +90,18 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.3.0" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8abf5d501fd757c2d2ee78d0cc40f606e92e3a63544420316565556ed28485e2" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" -version = "0.23.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", diff --git a/Cargo.toml b/Cargo.toml index a6e40dc..d6c113d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ datasketches = { path = "datasketches" } clap = { version = "4.5.20", features = ["derive"] } insta = { version = "1.46.1" } googletest = { version = "0.14.2" } -cargo_metadata = { version = "0.23.1" } +cargo_metadata = { version = "0.19.1" } which = { version = "8.0.0" } [workspace.lints.rust] diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 57a02b0..5bcaaac 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -53,8 +53,7 @@ struct CommandTest { impl CommandTest { fn run(self) { let features = sketches_features(); - let features_refs: Vec<&str> = features.iter().map(|s| s.as_str()).collect(); - run_command(make_test_cmd(self.no_capture, &features_refs)); + run_command(make_test_cmd(self.no_capture, &features)); } } @@ -122,7 +121,7 @@ fn run_command(mut cmd: StdCommand) { assert!(status.success(), "command failed: {status}"); } -fn make_test_cmd(no_capture: bool, features: &[&str]) -> StdCommand { +fn make_test_cmd(no_capture: bool, features: &[String]) -> StdCommand { let mut cmd = find_command("cargo"); cmd.args(["test", "--workspace", "--no-default-features"]); if !features.is_empty() { From f72436e411ee84652c5ca3913d8e84baf543e67e Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 20:32:06 +0800 Subject: [PATCH 06/16] avoid warnings Signed-off-by: tison --- datasketches/src/codec/assert.rs | 37 +++++++++++++++++++++------ datasketches/src/codec/family.rs | 7 ++++++ datasketches/src/codec/mod.rs | 20 ++++++++++++++- datasketches/src/common/mod.rs | 4 ++- datasketches/src/error.rs | 43 ++++++++++++++++++++++++++++++++ datasketches/src/hash/mod.rs | 37 +++++++++++++++++++++++++-- datasketches/src/lib.rs | 5 +--- 7 files changed, 138 insertions(+), 15 deletions(-) diff --git a/datasketches/src/codec/assert.rs b/datasketches/src/codec/assert.rs index 2ef5ee5..b16ce05 100644 --- a/datasketches/src/codec/assert.rs +++ b/datasketches/src/codec/assert.rs @@ -15,15 +15,29 @@ // specific language governing permissions and limitations // under the License. -use std::collections::Bound; -use std::ops::RangeBounds; - use crate::error::Error; +#[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "tdigest", + feature = "theta" +))] pub(crate) fn insufficient_data(tag: &'static str) -> impl FnOnce(std::io::Error) -> Error { move |_| Error::insufficient_data(tag) } +#[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "tdigest" +))] pub(crate) fn ensure_serial_version_is(expected: u8, actual: u8) -> Result<(), Error> { if expected == actual { Ok(()) @@ -34,6 +48,12 @@ pub(crate) fn ensure_serial_version_is(expected: u8, actual: u8) -> Result<(), E } } +#[cfg(any( + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "tdigest" +))] pub(crate) fn ensure_preamble_longs_in(expected: &[u8], actual: u8) -> Result<(), Error> { if expected.contains(&actual) { Ok(()) @@ -42,10 +62,13 @@ pub(crate) fn ensure_preamble_longs_in(expected: &[u8], actual: u8) -> Result<() } } -pub(crate) fn ensure_preamble_longs_in_range( - expected: impl RangeBounds, - actual: u8, -) -> Result<(), Error> { +#[cfg(any(feature = "bloom", feature = "theta"))] +pub(crate) fn ensure_preamble_longs_in_range(expected: R, actual: u8) -> Result<(), Error> +where + R: std::ops::RangeBounds, +{ + use std::collections::Bound; + let start = expected.start_bound(); let end = expected.end_bound(); if expected.contains(&actual) { diff --git a/datasketches/src/codec/family.rs b/datasketches/src/codec/family.rs index 21cf2b4..1f1ade3 100644 --- a/datasketches/src/codec/family.rs +++ b/datasketches/src/codec/family.rs @@ -34,6 +34,7 @@ pub struct Family { impl Family { /// Theta Sketch for cardinality estimation. + #[cfg(feature = "theta")] pub const THETA: Family = Family { id: 3, name: "THETA", @@ -42,6 +43,7 @@ impl Family { }; /// The HLL family of sketches. + #[cfg(feature = "hll")] pub const HLL: Family = Family { id: 7, name: "HLL", @@ -50,6 +52,7 @@ impl Family { }; /// The Frequency family of sketches. + #[cfg(feature = "frequencies")] pub const FREQUENCY: Family = Family { id: 10, name: "FREQUENCY", @@ -58,6 +61,7 @@ impl Family { }; /// Compressed Probabilistic Counting (CPC) Sketch. + #[cfg(feature = "cpc")] pub const CPC: Family = Family { id: 16, name: "CPC", @@ -66,6 +70,7 @@ impl Family { }; /// CountMin Sketch + #[cfg(feature = "countmin")] pub const COUNTMIN: Family = Family { id: 18, name: "COUNTMIN", @@ -74,6 +79,7 @@ impl Family { }; /// T-Digest for estimating quantiles and ranks. + #[cfg(feature = "tdigest")] pub const TDIGEST: Family = Family { id: 20, name: "TDIGEST", @@ -82,6 +88,7 @@ impl Family { }; /// Bloom Filter. + #[cfg(feature = "bloom")] pub const BLOOMFILTER: Family = Family { id: 21, name: "BLOOMFILTER", diff --git a/datasketches/src/codec/mod.rs b/datasketches/src/codec/mod.rs index 28008ff..d3298ca 100644 --- a/datasketches/src/codec/mod.rs +++ b/datasketches/src/codec/mod.rs @@ -23,6 +23,24 @@ mod encode; pub use self::decode::SketchSlice; pub use self::encode::SketchBytes; -// private to datasketches crate +// private internal modules +#[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "tdigest", + feature = "theta" +))] pub(crate) mod assert; +#[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "tdigest", + feature = "theta" +))] pub(crate) mod family; diff --git a/datasketches/src/common/mod.rs b/datasketches/src/common/mod.rs index 36b734f..bd48460 100644 --- a/datasketches/src/common/mod.rs +++ b/datasketches/src/common/mod.rs @@ -23,6 +23,8 @@ mod resize; pub use self::num_std_dev::NumStdDev; pub use self::resize::ResizeFactor; -// private to datasketches crate +// private internal modules +#[cfg(feature = "theta")] pub(crate) mod binomial_bounds; +#[cfg(feature = "cpc")] pub(crate) mod inv_pow2_table; diff --git a/datasketches/src/error.rs b/datasketches/src/error.rs index ee282aa..384c5e9 100644 --- a/datasketches/src/error.rs +++ b/datasketches/src/error.rs @@ -91,28 +91,71 @@ impl Error { // Convenient constructors used within datasketches crate. impl Error { + #[cfg(feature = "theta")] pub(crate) fn invalid_argument(msg: impl Into) -> Self { Self::new(ErrorKind::InvalidArgument, msg) } + #[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "tdigest", + feature = "theta" + ))] pub(crate) fn deserial(msg: impl Into) -> Self { Self::new(ErrorKind::InvalidData, msg) } + #[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "tdigest", + feature = "theta" + ))] pub(crate) fn insufficient_data(msg: impl fmt::Display) -> Self { Self::deserial(format!("insufficient data: {msg}")) } + #[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "tdigest", + feature = "theta" + ))] pub(crate) fn insufficient_data_of(context: &'static str, msg: impl fmt::Display) -> Self { Self::deserial(format!("insufficient data ({context}): {msg}")) } + #[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "tdigest", + feature = "theta" + ))] pub(crate) fn invalid_family(expected: u8, actual: u8, name: &'static str) -> Self { Self::deserial(format!( "invalid family: expected {expected} ({name}), got {actual}" )) } + #[cfg(any( + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "tdigest" + ))] pub(crate) fn invalid_preamble_longs(expected: &[u8], actual: u8) -> Self { Error::deserial(format!( "invalid preamble longs: expected {expected:?}, got {actual}" diff --git a/datasketches/src/hash/mod.rs b/datasketches/src/hash/mod.rs index 99d2cca..0a77759 100644 --- a/datasketches/src/hash/mod.rs +++ b/datasketches/src/hash/mod.rs @@ -15,10 +15,26 @@ // specific language governing permissions and limitations // under the License. +#[cfg(any( + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "theta" +))] mod murmurhash; -mod xxhash; - +#[cfg(any( + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "theta" +))] pub(crate) use self::murmurhash::MurmurHash3X64128; + +#[cfg(feature = "bloom")] +mod xxhash; +#[cfg(feature = "bloom")] pub(crate) use self::xxhash::XxHash64; /// The seed 9001 used in the sketch update methods is a prime number that was chosen very early @@ -34,6 +50,14 @@ pub(crate) use self::xxhash::XxHash64; /// and seed are identical for both sketches, otherwise the assumed 1:1 relationship between the /// original source key value and the hashed bit string would be violated. Once you have developed /// a history of stored sketches you are stuck with it. +#[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "theta" +))] pub(crate) const DEFAULT_UPDATE_SEED: u64 = 9001; /// Computes and checks the 16-bit seed hash from the given long seed. @@ -44,6 +68,7 @@ pub(crate) const DEFAULT_UPDATE_SEED: u64 = 9001; /// # Panics /// /// Panics if the computed seed hash is zero. +#[cfg(any(feature = "countmin", feature = "cpc", feature = "theta"))] pub(crate) fn compute_seed_hash(seed: u64) -> u16 { use std::hash::Hasher; @@ -60,6 +85,14 @@ pub(crate) fn compute_seed_hash(seed: u64) -> u16 { /// # Panics /// /// Panics if `bytes.len()` is greater than 8. +#[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "theta" +))] fn read_u64_le(bytes: &[u8]) -> u64 { let mut buf = [0u8; 8]; buf[..bytes.len()].copy_from_slice(bytes); diff --git a/datasketches/src/lib.rs b/datasketches/src/lib.rs index 833d463..65f0ddd 100644 --- a/datasketches/src/lib.rs +++ b/datasketches/src/lib.rs @@ -31,7 +31,6 @@ compile_error!("datasketches does not support big-endian targets"); // sketches modules - #[cfg(feature = "bloom")] pub mod bloom; #[cfg(feature = "countmin")] @@ -47,13 +46,11 @@ pub mod tdigest; #[cfg(feature = "theta")] pub mod theta; -// common used modules - +// common modules pub mod codec; pub mod common; pub mod error; pub mod hash_value; // private internal modules - mod hash; From f4825c3020d2b90b399f3dc8152b7b5f9155beef Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 21:16:49 +0800 Subject: [PATCH 07/16] codex round Signed-off-by: tison --- datasketches/src/codec/assert.rs | 39 +++++-------------- datasketches/src/codec/family.rs | 9 +---- datasketches/src/error.rs | 46 +--------------------- datasketches/src/hash/mod.rs | 39 +++---------------- datasketches/src/lib.rs | 8 ++++ xtask/src/main.rs | 65 ++++++++++++++++++++++++++++++-- 6 files changed, 89 insertions(+), 117 deletions(-) diff --git a/datasketches/src/codec/assert.rs b/datasketches/src/codec/assert.rs index b16ce05..090cf47 100644 --- a/datasketches/src/codec/assert.rs +++ b/datasketches/src/codec/assert.rs @@ -15,29 +15,17 @@ // specific language governing permissions and limitations // under the License. +#![allow(dead_code)] + +use std::collections::Bound; +use std::ops::RangeBounds; + use crate::error::Error; -#[cfg(any( - feature = "bloom", - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "hll", - feature = "tdigest", - feature = "theta" -))] pub(crate) fn insufficient_data(tag: &'static str) -> impl FnOnce(std::io::Error) -> Error { move |_| Error::insufficient_data(tag) } -#[cfg(any( - feature = "bloom", - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "hll", - feature = "tdigest" -))] pub(crate) fn ensure_serial_version_is(expected: u8, actual: u8) -> Result<(), Error> { if expected == actual { Ok(()) @@ -48,12 +36,6 @@ pub(crate) fn ensure_serial_version_is(expected: u8, actual: u8) -> Result<(), E } } -#[cfg(any( - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "tdigest" -))] pub(crate) fn ensure_preamble_longs_in(expected: &[u8], actual: u8) -> Result<(), Error> { if expected.contains(&actual) { Ok(()) @@ -62,13 +44,10 @@ pub(crate) fn ensure_preamble_longs_in(expected: &[u8], actual: u8) -> Result<() } } -#[cfg(any(feature = "bloom", feature = "theta"))] -pub(crate) fn ensure_preamble_longs_in_range(expected: R, actual: u8) -> Result<(), Error> -where - R: std::ops::RangeBounds, -{ - use std::collections::Bound; - +pub(crate) fn ensure_preamble_longs_in_range( + expected: impl RangeBounds, + actual: u8, +) -> Result<(), Error> { let start = expected.start_bound(); let end = expected.end_bound(); if expected.contains(&actual) { diff --git a/datasketches/src/codec/family.rs b/datasketches/src/codec/family.rs index 1f1ade3..e04019b 100644 --- a/datasketches/src/codec/family.rs +++ b/datasketches/src/codec/family.rs @@ -21,6 +21,7 @@ use crate::error::Error; /// /// A family defines a set of classes that share fundamental algorithms and behaviors. The classes /// within a family may still differ by how they are stored and accessed. +#[allow(dead_code)] pub struct Family { /// The byte ID for this family. pub id: u8, @@ -32,9 +33,9 @@ pub struct Family { pub max_pre_longs: u8, } +#[allow(dead_code)] impl Family { /// Theta Sketch for cardinality estimation. - #[cfg(feature = "theta")] pub const THETA: Family = Family { id: 3, name: "THETA", @@ -43,7 +44,6 @@ impl Family { }; /// The HLL family of sketches. - #[cfg(feature = "hll")] pub const HLL: Family = Family { id: 7, name: "HLL", @@ -52,7 +52,6 @@ impl Family { }; /// The Frequency family of sketches. - #[cfg(feature = "frequencies")] pub const FREQUENCY: Family = Family { id: 10, name: "FREQUENCY", @@ -61,7 +60,6 @@ impl Family { }; /// Compressed Probabilistic Counting (CPC) Sketch. - #[cfg(feature = "cpc")] pub const CPC: Family = Family { id: 16, name: "CPC", @@ -70,7 +68,6 @@ impl Family { }; /// CountMin Sketch - #[cfg(feature = "countmin")] pub const COUNTMIN: Family = Family { id: 18, name: "COUNTMIN", @@ -79,7 +76,6 @@ impl Family { }; /// T-Digest for estimating quantiles and ranks. - #[cfg(feature = "tdigest")] pub const TDIGEST: Family = Family { id: 20, name: "TDIGEST", @@ -88,7 +84,6 @@ impl Family { }; /// Bloom Filter. - #[cfg(feature = "bloom")] pub const BLOOMFILTER: Family = Family { id: 21, name: "BLOOMFILTER", diff --git a/datasketches/src/error.rs b/datasketches/src/error.rs index 384c5e9..9ce1aef 100644 --- a/datasketches/src/error.rs +++ b/datasketches/src/error.rs @@ -89,73 +89,31 @@ impl Error { } } -// Convenient constructors used within datasketches crate. +// Convenient constructors used by different sketches under different feature combinations. +#[allow(dead_code)] impl Error { - #[cfg(feature = "theta")] pub(crate) fn invalid_argument(msg: impl Into) -> Self { Self::new(ErrorKind::InvalidArgument, msg) } - #[cfg(any( - feature = "bloom", - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "hll", - feature = "tdigest", - feature = "theta" - ))] pub(crate) fn deserial(msg: impl Into) -> Self { Self::new(ErrorKind::InvalidData, msg) } - #[cfg(any( - feature = "bloom", - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "hll", - feature = "tdigest", - feature = "theta" - ))] pub(crate) fn insufficient_data(msg: impl fmt::Display) -> Self { Self::deserial(format!("insufficient data: {msg}")) } - #[cfg(any( - feature = "bloom", - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "hll", - feature = "tdigest", - feature = "theta" - ))] pub(crate) fn insufficient_data_of(context: &'static str, msg: impl fmt::Display) -> Self { Self::deserial(format!("insufficient data ({context}): {msg}")) } - #[cfg(any( - feature = "bloom", - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "hll", - feature = "tdigest", - feature = "theta" - ))] pub(crate) fn invalid_family(expected: u8, actual: u8, name: &'static str) -> Self { Self::deserial(format!( "invalid family: expected {expected} ({name}), got {actual}" )) } - #[cfg(any( - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "tdigest" - ))] pub(crate) fn invalid_preamble_longs(expected: &[u8], actual: u8) -> Self { Error::deserial(format!( "invalid preamble longs: expected {expected:?}, got {actual}" diff --git a/datasketches/src/hash/mod.rs b/datasketches/src/hash/mod.rs index 0a77759..9c9ffd7 100644 --- a/datasketches/src/hash/mod.rs +++ b/datasketches/src/hash/mod.rs @@ -15,26 +15,14 @@ // specific language governing permissions and limitations // under the License. -#[cfg(any( - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "hll", - feature = "theta" -))] +#![allow(dead_code)] + mod murmurhash; -#[cfg(any( - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "hll", - feature = "theta" -))] +#[allow(unused_imports)] pub(crate) use self::murmurhash::MurmurHash3X64128; -#[cfg(feature = "bloom")] mod xxhash; -#[cfg(feature = "bloom")] +#[allow(unused_imports)] pub(crate) use self::xxhash::XxHash64; /// The seed 9001 used in the sketch update methods is a prime number that was chosen very early @@ -50,14 +38,7 @@ pub(crate) use self::xxhash::XxHash64; /// and seed are identical for both sketches, otherwise the assumed 1:1 relationship between the /// original source key value and the hashed bit string would be violated. Once you have developed /// a history of stored sketches you are stuck with it. -#[cfg(any( - feature = "bloom", - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "hll", - feature = "theta" -))] +#[allow(dead_code)] pub(crate) const DEFAULT_UPDATE_SEED: u64 = 9001; /// Computes and checks the 16-bit seed hash from the given long seed. @@ -68,7 +49,7 @@ pub(crate) const DEFAULT_UPDATE_SEED: u64 = 9001; /// # Panics /// /// Panics if the computed seed hash is zero. -#[cfg(any(feature = "countmin", feature = "cpc", feature = "theta"))] +#[allow(dead_code)] pub(crate) fn compute_seed_hash(seed: u64) -> u16 { use std::hash::Hasher; @@ -85,14 +66,6 @@ pub(crate) fn compute_seed_hash(seed: u64) -> u16 { /// # Panics /// /// Panics if `bytes.len()` is greater than 8. -#[cfg(any( - feature = "bloom", - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "hll", - feature = "theta" -))] fn read_u64_le(bytes: &[u8]) -> u64 { let mut buf = [0u8; 8]; buf[..bytes.len()].copy_from_slice(bytes); diff --git a/datasketches/src/lib.rs b/datasketches/src/lib.rs index 65f0ddd..97ca559 100644 --- a/datasketches/src/lib.rs +++ b/datasketches/src/lib.rs @@ -53,4 +53,12 @@ pub mod error; pub mod hash_value; // private internal modules +#[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "theta" +))] mod hash; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 5bcaaac..c2a6d87 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -30,6 +30,7 @@ struct Command { impl Command { fn run(self) { match self.sub { + SubCommand::FeatureMatrix(cmd) => cmd.run(), SubCommand::Lint(cmd) => cmd.run(), SubCommand::Test(cmd) => cmd.run(), } @@ -38,12 +39,36 @@ impl Command { #[derive(Subcommand)] enum SubCommand { + #[clap( + about = "Check datasketches under the no-feature, single-feature, and all-feature matrix." + )] + FeatureMatrix(CommandFeatureMatrix), #[clap(about = "Run format and clippy checks.")] Lint(CommandLint), #[clap(about = "Run unit tests.")] Test(CommandTest), } +#[derive(Parser)] +#[clap(name = "feature-matrix")] +struct CommandFeatureMatrix {} + +impl CommandFeatureMatrix { + fn run(self) { + let features = datasketches_features(); + + run_command(make_feature_matrix_check_cmd(FeatureSelection::None)); + + for feature in &features { + run_command(make_feature_matrix_check_cmd(FeatureSelection::One( + feature.as_str(), + ))); + } + + run_command(make_feature_matrix_check_cmd(FeatureSelection::All)); + } +} + #[derive(Parser)] struct CommandTest { #[arg(long, help = "Run tests serially and do not capture output.")] @@ -52,12 +77,12 @@ struct CommandTest { impl CommandTest { fn run(self) { - let features = sketches_features(); + let features = datasketches_features(); run_command(make_test_cmd(self.no_capture, &features)); } } -fn sketches_features() -> Vec { +fn datasketches_features() -> Vec { use cargo_metadata::Metadata; use cargo_metadata::MetadataCommand; @@ -73,7 +98,19 @@ fn sketches_features() -> Vec { .find(|p| p.name == "datasketches") .expect("failed to find datasketches package"); - pkg.features.into_keys().collect() + let mut features = pkg + .features + .into_keys() + .filter(|feature| feature != "default") + .collect::>(); + features.sort(); + features +} + +enum FeatureSelection<'a> { + None, + One(&'a str), + All, } #[derive(Parser)] @@ -133,6 +170,28 @@ fn make_test_cmd(no_capture: bool, features: &[String]) -> StdCommand { cmd } +fn make_feature_matrix_check_cmd(features: FeatureSelection<'_>) -> StdCommand { + let mut cmd = find_command("cargo"); + cmd.env("RUSTFLAGS", "-Dwarnings"); + cmd.args([ + "check", + "--package", + "datasketches", + "--all-targets", + "--no-default-features", + ]); + match features { + FeatureSelection::None => {} + FeatureSelection::One(feature) => { + cmd.args(["--features", feature]); + } + FeatureSelection::All => { + cmd.arg("--all-features"); + } + } + cmd +} + fn make_format_cmd(fix: bool) -> StdCommand { let mut cmd = find_command("cargo"); cmd.args(["+nightly", "fmt", "--all"]); From 64df437ced9b587424adc86d7d7e201cf4f68546 Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 22:16:00 +0800 Subject: [PATCH 08/16] fixup Signed-off-by: tison --- .github/workflows/ci.yml | 3 +++ datasketches/src/codec/assert.rs | 2 -- datasketches/src/codec/family.rs | 2 -- datasketches/src/codec/mod.rs | 4 ++-- datasketches/src/common/mod.rs | 2 -- datasketches/src/error.rs | 4 ++-- datasketches/src/hash/mod.rs | 6 ------ xtask/src/main.rs | 14 ++++++-------- 8 files changed, 13 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87878f8..833840e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,6 +93,9 @@ jobs: - name: Prepare test data shell: bash run: ./tools/generate_serialization_test_data.py + - name: Check feature matrix + shell: bash + run: cargo x check - name: Run unit tests shell: bash run: cargo x test diff --git a/datasketches/src/codec/assert.rs b/datasketches/src/codec/assert.rs index 090cf47..2ef5ee5 100644 --- a/datasketches/src/codec/assert.rs +++ b/datasketches/src/codec/assert.rs @@ -15,8 +15,6 @@ // specific language governing permissions and limitations // under the License. -#![allow(dead_code)] - use std::collections::Bound; use std::ops::RangeBounds; diff --git a/datasketches/src/codec/family.rs b/datasketches/src/codec/family.rs index e04019b..21cf2b4 100644 --- a/datasketches/src/codec/family.rs +++ b/datasketches/src/codec/family.rs @@ -21,7 +21,6 @@ use crate::error::Error; /// /// A family defines a set of classes that share fundamental algorithms and behaviors. The classes /// within a family may still differ by how they are stored and accessed. -#[allow(dead_code)] pub struct Family { /// The byte ID for this family. pub id: u8, @@ -33,7 +32,6 @@ pub struct Family { pub max_pre_longs: u8, } -#[allow(dead_code)] impl Family { /// Theta Sketch for cardinality estimation. pub const THETA: Family = Family { diff --git a/datasketches/src/codec/mod.rs b/datasketches/src/codec/mod.rs index d3298ca..5b78f53 100644 --- a/datasketches/src/codec/mod.rs +++ b/datasketches/src/codec/mod.rs @@ -17,13 +17,11 @@ //! Codec utilities for datasketches crate. -// public common codec utilities for datasketches crate mod decode; mod encode; pub use self::decode::SketchSlice; pub use self::encode::SketchBytes; -// private internal modules #[cfg(any( feature = "bloom", feature = "countmin", @@ -33,7 +31,9 @@ pub use self::encode::SketchBytes; feature = "tdigest", feature = "theta" ))] +#[allow(dead_code)] // some utilities are only used for certain sketches pub(crate) mod assert; + #[cfg(any( feature = "bloom", feature = "countmin", diff --git a/datasketches/src/common/mod.rs b/datasketches/src/common/mod.rs index bd48460..503df72 100644 --- a/datasketches/src/common/mod.rs +++ b/datasketches/src/common/mod.rs @@ -17,13 +17,11 @@ //! Data structures and functions that may be used across all the sketch families. -// public common components for datasketches crate mod num_std_dev; mod resize; pub use self::num_std_dev::NumStdDev; pub use self::resize::ResizeFactor; -// private internal modules #[cfg(feature = "theta")] pub(crate) mod binomial_bounds; #[cfg(feature = "cpc")] diff --git a/datasketches/src/error.rs b/datasketches/src/error.rs index 9ce1aef..8ec5c10 100644 --- a/datasketches/src/error.rs +++ b/datasketches/src/error.rs @@ -89,8 +89,8 @@ impl Error { } } -// Convenient constructors used by different sketches under different feature combinations. -#[allow(dead_code)] +// pub(crate) convenient constructors +#[allow(dead_code)] // some constructors are only used for certain sketches impl Error { pub(crate) fn invalid_argument(msg: impl Into) -> Self { Self::new(ErrorKind::InvalidArgument, msg) diff --git a/datasketches/src/hash/mod.rs b/datasketches/src/hash/mod.rs index 9c9ffd7..e11fca6 100644 --- a/datasketches/src/hash/mod.rs +++ b/datasketches/src/hash/mod.rs @@ -15,14 +15,10 @@ // specific language governing permissions and limitations // under the License. -#![allow(dead_code)] - mod murmurhash; -#[allow(unused_imports)] pub(crate) use self::murmurhash::MurmurHash3X64128; mod xxhash; -#[allow(unused_imports)] pub(crate) use self::xxhash::XxHash64; /// The seed 9001 used in the sketch update methods is a prime number that was chosen very early @@ -38,7 +34,6 @@ pub(crate) use self::xxhash::XxHash64; /// and seed are identical for both sketches, otherwise the assumed 1:1 relationship between the /// original source key value and the hashed bit string would be violated. Once you have developed /// a history of stored sketches you are stuck with it. -#[allow(dead_code)] pub(crate) const DEFAULT_UPDATE_SEED: u64 = 9001; /// Computes and checks the 16-bit seed hash from the given long seed. @@ -49,7 +44,6 @@ pub(crate) const DEFAULT_UPDATE_SEED: u64 = 9001; /// # Panics /// /// Panics if the computed seed hash is zero. -#[allow(dead_code)] pub(crate) fn compute_seed_hash(seed: u64) -> u16 { use std::hash::Hasher; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index c2a6d87..c720795 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -30,7 +30,7 @@ struct Command { impl Command { fn run(self) { match self.sub { - SubCommand::FeatureMatrix(cmd) => cmd.run(), + SubCommand::Check(cmd) => cmd.run(), SubCommand::Lint(cmd) => cmd.run(), SubCommand::Test(cmd) => cmd.run(), } @@ -39,10 +39,8 @@ impl Command { #[derive(Subcommand)] enum SubCommand { - #[clap( - about = "Check datasketches under the no-feature, single-feature, and all-feature matrix." - )] - FeatureMatrix(CommandFeatureMatrix), + #[clap(about = "Check datasketches under the feature matrix.")] + Check(CommandCheck), #[clap(about = "Run format and clippy checks.")] Lint(CommandLint), #[clap(about = "Run unit tests.")] @@ -50,10 +48,10 @@ enum SubCommand { } #[derive(Parser)] -#[clap(name = "feature-matrix")] -struct CommandFeatureMatrix {} +#[clap(name = "check")] +struct CommandCheck {} -impl CommandFeatureMatrix { +impl CommandCheck { fn run(self) { let features = datasketches_features(); From b67f502e81ae4dc5f36b33c8fe0ca420652c86d5 Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 22:32:40 +0800 Subject: [PATCH 09/16] fixup Signed-off-by: tison --- datasketches/src/hash/mod.rs | 37 ++++++++++++++++++++++++++++++++++++ datasketches/src/lib.rs | 8 -------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/datasketches/src/hash/mod.rs b/datasketches/src/hash/mod.rs index e11fca6..f58a1ec 100644 --- a/datasketches/src/hash/mod.rs +++ b/datasketches/src/hash/mod.rs @@ -15,10 +15,26 @@ // specific language governing permissions and limitations // under the License. +#[cfg(any( + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "theta" +))] mod murmurhash; +#[cfg(any( + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "theta" +))] pub(crate) use self::murmurhash::MurmurHash3X64128; +#[cfg(any(feature = "bloom"))] mod xxhash; +#[cfg(any(feature = "bloom"))] pub(crate) use self::xxhash::XxHash64; /// The seed 9001 used in the sketch update methods is a prime number that was chosen very early @@ -34,6 +50,14 @@ pub(crate) use self::xxhash::XxHash64; /// and seed are identical for both sketches, otherwise the assumed 1:1 relationship between the /// original source key value and the hashed bit string would be violated. Once you have developed /// a history of stored sketches you are stuck with it. +#[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "theta" +))] pub(crate) const DEFAULT_UPDATE_SEED: u64 = 9001; /// Computes and checks the 16-bit seed hash from the given long seed. @@ -44,6 +68,11 @@ pub(crate) const DEFAULT_UPDATE_SEED: u64 = 9001; /// # Panics /// /// Panics if the computed seed hash is zero. +#[cfg(any( + feature = "countmin", + feature = "cpc", + feature = "theta" +))] pub(crate) fn compute_seed_hash(seed: u64) -> u16 { use std::hash::Hasher; @@ -60,6 +89,14 @@ pub(crate) fn compute_seed_hash(seed: u64) -> u16 { /// # Panics /// /// Panics if `bytes.len()` is greater than 8. +#[cfg(any( + feature = "bloom", + feature = "countmin", + feature = "cpc", + feature = "frequencies", + feature = "hll", + feature = "theta" +))] fn read_u64_le(bytes: &[u8]) -> u64 { let mut buf = [0u8; 8]; buf[..bytes.len()].copy_from_slice(bytes); diff --git a/datasketches/src/lib.rs b/datasketches/src/lib.rs index 97ca559..65f0ddd 100644 --- a/datasketches/src/lib.rs +++ b/datasketches/src/lib.rs @@ -53,12 +53,4 @@ pub mod error; pub mod hash_value; // private internal modules -#[cfg(any( - feature = "bloom", - feature = "countmin", - feature = "cpc", - feature = "frequencies", - feature = "hll", - feature = "theta" -))] mod hash; From acac45d625b009f0cd3ee14161c97494385662f3 Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 22:36:51 +0800 Subject: [PATCH 10/16] fixup Signed-off-by: tison --- datasketches/src/codec/family.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/datasketches/src/codec/family.rs b/datasketches/src/codec/family.rs index 21cf2b4..1f1ade3 100644 --- a/datasketches/src/codec/family.rs +++ b/datasketches/src/codec/family.rs @@ -34,6 +34,7 @@ pub struct Family { impl Family { /// Theta Sketch for cardinality estimation. + #[cfg(feature = "theta")] pub const THETA: Family = Family { id: 3, name: "THETA", @@ -42,6 +43,7 @@ impl Family { }; /// The HLL family of sketches. + #[cfg(feature = "hll")] pub const HLL: Family = Family { id: 7, name: "HLL", @@ -50,6 +52,7 @@ impl Family { }; /// The Frequency family of sketches. + #[cfg(feature = "frequencies")] pub const FREQUENCY: Family = Family { id: 10, name: "FREQUENCY", @@ -58,6 +61,7 @@ impl Family { }; /// Compressed Probabilistic Counting (CPC) Sketch. + #[cfg(feature = "cpc")] pub const CPC: Family = Family { id: 16, name: "CPC", @@ -66,6 +70,7 @@ impl Family { }; /// CountMin Sketch + #[cfg(feature = "countmin")] pub const COUNTMIN: Family = Family { id: 18, name: "COUNTMIN", @@ -74,6 +79,7 @@ impl Family { }; /// T-Digest for estimating quantiles and ranks. + #[cfg(feature = "tdigest")] pub const TDIGEST: Family = Family { id: 20, name: "TDIGEST", @@ -82,6 +88,7 @@ impl Family { }; /// Bloom Filter. + #[cfg(feature = "bloom")] pub const BLOOMFILTER: Family = Family { id: 21, name: "BLOOMFILTER", From 20ef0bc3ffa0ffe91cc9be399ca1ef8faab23297 Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 22:37:58 +0800 Subject: [PATCH 11/16] fixup Signed-off-by: tison --- datasketches/src/codec/family.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datasketches/src/codec/family.rs b/datasketches/src/codec/family.rs index 1f1ade3..cc1ce45 100644 --- a/datasketches/src/codec/family.rs +++ b/datasketches/src/codec/family.rs @@ -27,8 +27,10 @@ pub struct Family { /// The name for this family. pub name: &'static str, /// The minimum preamble size for this family in longs (8-bytes integer). + #[allow(dead_code)] // only some sketches need to check this field pub min_pre_longs: u8, /// The maximum preamble size for this family in longs (8-bytes integer). + #[allow(dead_code)] // only some sketches need to check this field pub max_pre_longs: u8, } From 31f2036b63f033f51cf7d725ff2880d29a9a82b0 Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 22:47:21 +0800 Subject: [PATCH 12/16] fixup more Signed-off-by: tison --- datasketches/src/hash/mod.rs | 10 +++------- xtask/src/main.rs | 35 ++++++++++------------------------- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/datasketches/src/hash/mod.rs b/datasketches/src/hash/mod.rs index f58a1ec..0a77759 100644 --- a/datasketches/src/hash/mod.rs +++ b/datasketches/src/hash/mod.rs @@ -32,9 +32,9 @@ mod murmurhash; ))] pub(crate) use self::murmurhash::MurmurHash3X64128; -#[cfg(any(feature = "bloom"))] +#[cfg(feature = "bloom")] mod xxhash; -#[cfg(any(feature = "bloom"))] +#[cfg(feature = "bloom")] pub(crate) use self::xxhash::XxHash64; /// The seed 9001 used in the sketch update methods is a prime number that was chosen very early @@ -68,11 +68,7 @@ pub(crate) const DEFAULT_UPDATE_SEED: u64 = 9001; /// # Panics /// /// Panics if the computed seed hash is zero. -#[cfg(any( - feature = "countmin", - feature = "cpc", - feature = "theta" -))] +#[cfg(any(feature = "countmin", feature = "cpc", feature = "theta"))] pub(crate) fn compute_seed_hash(seed: u64) -> u16 { use std::hash::Hasher; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index c720795..a50e512 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -55,15 +55,11 @@ impl CommandCheck { fn run(self) { let features = datasketches_features(); - run_command(make_feature_matrix_check_cmd(FeatureSelection::None)); - + run_command(make_check_cmd::(&[])); for feature in &features { - run_command(make_feature_matrix_check_cmd(FeatureSelection::One( - feature.as_str(), - ))); + run_command(make_check_cmd(&[feature])); } - - run_command(make_feature_matrix_check_cmd(FeatureSelection::All)); + run_command(make_check_cmd(&features)); } } @@ -105,12 +101,6 @@ fn datasketches_features() -> Vec { features } -enum FeatureSelection<'a> { - None, - One(&'a str), - All, -} - #[derive(Parser)] #[clap(name = "lint")] struct CommandLint { @@ -156,11 +146,11 @@ fn run_command(mut cmd: StdCommand) { assert!(status.success(), "command failed: {status}"); } -fn make_test_cmd(no_capture: bool, features: &[String]) -> StdCommand { +fn make_test_cmd>(no_capture: bool, features: &[T]) -> StdCommand { let mut cmd = find_command("cargo"); cmd.args(["test", "--workspace", "--no-default-features"]); - if !features.is_empty() { - cmd.args(["--features", features.join(",").as_str()]); + for feature in features { + cmd.args(["--features", feature.as_ref()]); } if no_capture { cmd.args(["--", "--nocapture"]); @@ -168,24 +158,19 @@ fn make_test_cmd(no_capture: bool, features: &[String]) -> StdCommand { cmd } -fn make_feature_matrix_check_cmd(features: FeatureSelection<'_>) -> StdCommand { +fn make_check_cmd>(features: &[T]) -> StdCommand { let mut cmd = find_command("cargo"); cmd.env("RUSTFLAGS", "-Dwarnings"); cmd.args([ + "+nightly", "check", "--package", "datasketches", "--all-targets", "--no-default-features", ]); - match features { - FeatureSelection::None => {} - FeatureSelection::One(feature) => { - cmd.args(["--features", feature]); - } - FeatureSelection::All => { - cmd.arg("--all-features"); - } + for feature in features { + cmd.args(["--features", feature.as_ref()]); } cmd } From 783eb1b36d1b59b418ccb17d3937fb7802b1a374 Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 22:54:58 +0800 Subject: [PATCH 13/16] fixup more Signed-off-by: tison --- CONTRIBUTING.md | 2 +- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- README.md | 4 ++-- rust-toolchain.toml | 2 +- xtask/src/main.rs | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b71cab9..dff59fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ Rustup will read the `rust-toolchain.toml` file and set up everything else autom ```shell cargo version -# cargo 1.85.0 ( 2024-12-31) +# cargo 1.86.0 ( ) ``` To keep code style consistent, run `cargo x lint --fix` to automatically fix any style issues before committing your changes. diff --git a/Cargo.lock b/Cargo.lock index a9c0fff..759784f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,18 +90,18 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.9" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +checksum = "8abf5d501fd757c2d2ee78d0cc40f606e92e3a63544420316565556ed28485e2" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" -version = "0.19.2" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ "camino", "cargo-platform", diff --git a/Cargo.toml b/Cargo.toml index d6c113d..82d7702 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ homepage = "https://datasketches.apache.org" license = "Apache-2.0" readme = "README.md" repository = "https://github.com/apache/datasketches-rust" -rust-version = "1.85.0" +rust-version = "1.86.0" [workspace.dependencies] # Workspace dependencies @@ -35,7 +35,7 @@ datasketches = { path = "datasketches" } clap = { version = "4.5.20", features = ["derive"] } insta = { version = "1.46.1" } googletest = { version = "0.14.2" } -cargo_metadata = { version = "0.19.1" } +cargo_metadata = { version = "0.23.1" } which = { version = "8.0.0" } [workspace.lints.rust] diff --git a/README.md b/README.md index bc1eabe..ad6d3b1 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ [![Crates.io][crates-badge]][crates-url] [![Documentation][docs-badge]][docs-url] -[![MSRV 1.85.0][msrv-badge]](https://www.whatrustisit.com) +[![MSRV 1.86.0][msrv-badge]](https://www.whatrustisit.com) [![Apache 2.0 licensed][license-badge]][license-url] [![Build Status][actions-badge]][actions-url] @@ -29,7 +29,7 @@ [crates-url]: https://crates.io/crates/datasketches [docs-badge]: https://img.shields.io/docsrs/datasketches [docs-url]: https://docs.rs/datasketches -[msrv-badge]: https://img.shields.io/badge/MSRV-1.85.0-green?logo=rust +[msrv-badge]: https://img.shields.io/badge/MSRV-1.86.0-green?logo=rust [license-badge]: https://img.shields.io/crates/l/datasketches [license-url]: LICENSE [actions-badge]: https://github.com/apache/datasketches-rust/workflows/CI/badge.svg diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a260d8d..7df2ea3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -16,5 +16,5 @@ # under the License. [toolchain] -channel = "1.85.0" +channel = "1.86.0" components = ["rustfmt", "clippy", "rust-analyzer"] diff --git a/xtask/src/main.rs b/xtask/src/main.rs index a50e512..04abaac 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -89,7 +89,7 @@ fn datasketches_features() -> Vec { let pkg = packages .into_iter() - .find(|p| p.name == "datasketches") + .find(|p| ^p.name.as_str() == "datasketches") .expect("failed to find datasketches package"); let mut features = pkg From 22d69a062856b3b635f4ed6b36135cfda603d8b8 Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 22:55:53 +0800 Subject: [PATCH 14/16] fixup more Signed-off-by: tison --- xtask/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 04abaac..a50e512 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -89,7 +89,7 @@ fn datasketches_features() -> Vec { let pkg = packages .into_iter() - .find(|p| ^p.name.as_str() == "datasketches") + .find(|p| p.name == "datasketches") .expect("failed to find datasketches package"); let mut features = pkg From bbf30d384b24319b5ff046936bac520f2cb60a2d Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 22:56:11 +0800 Subject: [PATCH 15/16] fixup more Signed-off-by: tison --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 759784f..f3212f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,9 +90,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8abf5d501fd757c2d2ee78d0cc40f606e92e3a63544420316565556ed28485e2" +checksum = "122ec45a44b270afd1402f351b782c676b173e3c3fb28d86ff7ebfb4d86a4ee4" dependencies = [ "serde", ] @@ -263,9 +263,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -286,7 +286,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] From 8963c5fdbafff0135126b53b0f4e068fff528241 Mon Sep 17 00:00:00 2001 From: tison Date: Sat, 9 May 2026 23:14:07 +0800 Subject: [PATCH 16/16] fixup Signed-off-by: tison --- .github/workflows/ci.yml | 7 +++---- xtask/src/main.rs | 14 +++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 833840e..9f59c71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,8 +52,10 @@ jobs: - uses: taiki-e/install-action@v2 with: tool: typos-cli,taplo-cli,hawkeye - - name: Check all + - name: Check linters run: cargo x lint + - name: Check feature matrix + run: cargo x check msrv: name: Resolve MSRV @@ -93,9 +95,6 @@ jobs: - name: Prepare test data shell: bash run: ./tools/generate_serialization_test_data.py - - name: Check feature matrix - shell: bash - run: cargo x check - name: Run unit tests shell: bash run: cargo x test diff --git a/xtask/src/main.rs b/xtask/src/main.rs index a50e512..260f426 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -55,9 +55,9 @@ impl CommandCheck { fn run(self) { let features = datasketches_features(); - run_command(make_check_cmd::(&[])); - for feature in &features { - run_command(make_check_cmd(&[feature])); + run_command(make_check_cmd(&[])); + for feature in features.chunks(1) { + run_command(make_check_cmd(feature)); } run_command(make_check_cmd(&features)); } @@ -146,11 +146,11 @@ fn run_command(mut cmd: StdCommand) { assert!(status.success(), "command failed: {status}"); } -fn make_test_cmd>(no_capture: bool, features: &[T]) -> StdCommand { +fn make_test_cmd(no_capture: bool, features: &[String]) -> StdCommand { let mut cmd = find_command("cargo"); cmd.args(["test", "--workspace", "--no-default-features"]); for feature in features { - cmd.args(["--features", feature.as_ref()]); + cmd.args(["--features", feature]); } if no_capture { cmd.args(["--", "--nocapture"]); @@ -158,7 +158,7 @@ fn make_test_cmd>(no_capture: bool, features: &[T]) -> StdCommand cmd } -fn make_check_cmd>(features: &[T]) -> StdCommand { +fn make_check_cmd(features: &[String]) -> StdCommand { let mut cmd = find_command("cargo"); cmd.env("RUSTFLAGS", "-Dwarnings"); cmd.args([ @@ -170,7 +170,7 @@ fn make_check_cmd>(features: &[T]) -> StdCommand { "--no-default-features", ]); for feature in features { - cmd.args(["--features", feature.as_ref()]); + cmd.args(["--features", feature]); } cmd }