
Rust Sota Arsenal
- 76 installs
- 62 repo stars
- Updated August 3, 2026
- terrylica/cc-skills
Helps with ai & agent building tasks.
About
rust-sota-arsenal is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- rust-sota-arsenal
- AI & Agent Building
- AI-coding skill
Rust Sota Arsenal by the numbers
- 76 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #5,410 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/terrylica/cc-skills --skill rust-sota-arsenalAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 76 |
|---|---|
| repo stars | ★ 62 |
| Last updated | August 3, 2026 |
| Repository | terrylica/cc-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Rust SOTA Arsenal
State-of-the-art Rust tooling knowledge for refactoring, profiling, benchmarking, testing, and SIMD optimization — tools that LLMs often lack deep training data on.
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
CRITICAL: Web-Verify Before Acting
The reference docs in this skill are a starting point, not ground truth. Tool versions, compatibility matrices, and API surfaces evolve faster than static docs. Before recommending specific versions or making upgrade decisions:
1. Check crates.io for latest versions: WebFetch the crates.io API to get current version info
WebFetch: https://crates.io/api/v1/crates/{crate_name}
Prompt: "What is the latest version? List recent versions."2. Check dependency compatibility: When upgrading (e.g., PyO3), verify downstream crate compatibility
WebFetch: https://crates.io/api/v1/crates/{crate_name}/{version}/dependencies
Prompt: "What version of {dependency} does this require?"3. Search for breaking changes: WebSearch for changelogs and migration guides
WebSearch: "{crate_name} latest version changelog migration"4. Fallback: Firecrawl scrape (if WebFetch fails or returns incomplete data — e.g., JS-heavy pages, rate limits):
curl -s -X POST http://littleblack:3002/v1/scrape \
-H "Content-Type: application/json" \
-d '{"url": "https://crates.io/crates/{crate_name}", "formats": ["markdown"], "waitFor": 0}' \
| jq -r '.data.markdown'Requires Tailscale connectivity. See /devops-tools:firecrawl-research-patterns for full API reference.
Why: The opendeviationbar-py session discovered PyO3 was at 0.28.2 (not 0.28) and pyo3-arrow at 0.17.0 only by web-searching — static docs would have led to wrong upgrade decisions.
When to Use
- Refactoring Rust code (AST-aware search/replace, API compatibility)
- Performance work (profiling, PGO, Cargo profile tuning)
- Benchmarking (choosing divan vs Criterion, setting up benchmarks)
- Testing (faster test runner, mutation testing, feature flag testing)
- SIMD optimization (portable SIMD on stable Rust)
- Migrating PyO3 bindings (0.22+)
Quick Reference
| Tool | Install | One-liner | Category |
|---|---|---|---|
ast-grep | cargo install ast-grep | AST-aware search/rewrite for Rust | Refactoring |
cargo-semver-checks | cargo install cargo-semver-checks | API compat linting (hundreds of lints) | Refactoring |
samply | cargo install samply | Profile → Firefox Profiler UI | Performance |
cargo-pgo | cargo install cargo-pgo | PGO + BOLT optimization | Performance |
cargo-wizard | cargo install cargo-wizard | Auto-configure Cargo profiles | Performance |
divan | divan = "<version>" in dev-deps | #[divan::bench] attribute API | Benchmarking |
criterion | criterion = "<version>" in dev-deps | Statistics-driven, Gnuplot reports | Benchmarking |
cargo-nextest | cargo install cargo-nextest | 3x faster, process-per-test | Testing |
cargo-mutants | cargo install cargo-mutants | Mutation testing (missed/caught) | Testing |
cargo-hack | cargo install cargo-hack | Feature powerset testing | Testing |
macerator | macerator = "<version>" in deps | Type-generic SIMD + multiversioning | SIMD |
cargo-audit | cargo install cargo-audit | RUSTSEC vulnerability scan | Dependencies |
cargo-deny | cargo install cargo-deny | License + advisory + ban | Dependencies |
cargo-vet | cargo install cargo-vet | Mozilla supply chain audit | Dependencies |
cargo-outdated | cargo install cargo-outdated | Dependency freshness | Dependencies |
cargo-geiger | cargo install cargo-geiger | Detect unsafe code in deps | Dependencies |
cargo-machete | cargo install cargo-machete | Find unused dependencies | Dependencies |
Refactoring Workflow
ast-grep: AST-Aware Search and Rewrite
When to use: Refactoring patterns across a codebase — safer than regex because it understands Rust syntax.
# Search for .unwrap() calls
ast-grep --pattern '$X.unwrap()' --lang rust
# Replace unwrap with expect
ast-grep --pattern '$X.unwrap()' --rewrite '$X.expect("TODO: handle error")' --lang rust
# Find unsafe blocks
ast-grep --pattern 'unsafe { $$$BODY }' --lang rust
# Convert match to if-let (single-arm + wildcard)
ast-grep --pattern 'match $X { $P => $E, _ => () }' --rewrite 'if let $P = $X { $E }' --lang rustFor complex multi-rule transforms, use YAML rule files. See ast-grep reference.
cargo-semver-checks: API Compatibility
When to use: Before publishing a crate version — catches accidental breaking changes.
# Check current changes against last published version
cargo semver-checks check-release
# Check against specific baseline
cargo semver-checks check-release --baseline-version 1.2.0
# Workspace mode
cargo semver-checks check-release --workspaceHundreds of built-in lints covering function removal, type changes, trait impl changes, and more (lint count grows with each release). See cargo-semver-checks reference.
Performance Workflow
Step 1: Profile with samply
# Build with debug info (release speed + symbols)
cargo build --release
# Profile (macOS — uses dtrace, needs SIP consideration)
samply record ./target/release/my-binary
# Opens Firefox Profiler UI in browser automatically
# Look for: hot functions, call trees, flame graphsSee samply reference for macOS dtrace setup and flame graph interpretation.
Step 2: Auto-configure profiles with cargo-wizard
# Interactive — choose optimization goal
cargo wizard
# Templates:
# 1. "fast-compile" — minimize build time (incremental, low opt)
# 2. "fast-runtime" — maximize performance (LTO, codegen-units=1)
# 3. "min-size" — minimize binary size (opt-level="z", LTO, strip)cargo-wizard writes directly to Cargo.toml [profile.*] sections. Endorsed by the Cargo team. See cargo-wizard reference.
Step 3: PGO + BOLT with cargo-pgo
Three-phase workflow for maximum performance:
# Phase 1: Instrument
cargo pgo build
# Phase 2: Collect profiles (run representative workload)
./target/release/my-binary < typical_input.txt
# Phase 3: Optimize with collected profiles
cargo pgo optimize
# Optional Phase 4: BOLT (post-link optimization, Linux only)
cargo pgo bolt optimizePGO typically gives 10-20% speedup on CPU-bound code. See cargo-pgo reference.
Benchmarking Workflow
divan vs Criterion — When to Use Which
| Aspect | divan | Criterion |
|---|---|---|
| API style | #[divan::bench] attribute | criterion_group! + criterion_main! macros |
| Setup | Add dep + #[divan::bench] | Add dep + benches/ dir + Cargo.toml [[bench]] |
| Generic benchmarks | Built-in #[divan::bench(types = [...])] | Manual with macros |
| Allocation profiling | Built-in AllocProfiler | Needs external tools |
| Reports | Terminal (colored) | HTML + Gnuplot graphs |
| CI integration | CodSpeed (native) | CodSpeed + criterion-compare |
| Maintenance | Maintained (check crates.io for cadence) | Active (criterion-rs organization) |
Recommendation: divan for new projects (simpler API); Criterion for existing projects or when HTML reports needed. See divan-and-criterion reference.
divan Quick Start
fn main() {
divan::main();
}
#[divan::bench]
fn my_benchmark(bencher: divan::Bencher) {
bencher.bench(|| {
// code to benchmark
});
}Criterion Quick Start
use criterion::{criterion_group, criterion_main, Criterion};
fn my_benchmark(c: &mut Criterion) {
c.bench_function("name", |b| {
b.iter(|| {
// code to benchmark
});
});
}
criterion_group!(benches, my_benchmark);
criterion_main!(benches);Testing Workflow
cargo-nextest: Faster Test Runner
# Run all tests (3x faster than cargo test)
cargo nextest run
# Run with specific profile
cargo nextest run --profile ci
# Retry flaky tests
cargo nextest run --retries 2
# JUnit XML output (for CI)
cargo nextest run --profile ci --message-format libtest-jsonConfig file: .config/nextest.toml. See cargo-nextest reference.
cargo-mutants: Mutation Testing
# Run mutation testing on entire crate
cargo mutants
# Filter to specific files/functions
cargo mutants --file src/parser.rs
cargo mutants --regex "parse_.*"
# Use nextest as test runner (faster)
cargo mutants -- --test-tool nextest
# Check results
cat mutants.out/missed.txt # Tests that didn't catch mutations
cat mutants.out/caught.txt # Tests that caught mutationsResult categories: caught (good), missed (weak test), timeout, unviable (won't compile). See cargo-mutants reference.
cargo-hack: Feature Flag Testing
# Test every feature individually
cargo hack test --each-feature
# Test all feature combinations (powerset)
cargo hack test --feature-powerset
# Exclude dev-dependencies (check only)
cargo hack check --feature-powerset --no-dev-deps
# CI: verify no feature combination breaks compilation
cargo hack check --feature-powerset --depth 2Essential for library crates with multiple features. See cargo-hack reference.
SIMD Decision Matrix
| Crate | Stable Rust | Type-Generic | Multiversioning | Maintained |
|---|---|---|---|---|
| macerator | Yes | Yes | Yes (stable) | Active |
wide | Yes | No (concrete types) | No | Active |
pulp | Yes | Yes | Yes | Superseded by macerator |
std::simd | Nightly only | Yes | No | Nightly-only (tracking issue: rust-lang/rust#86656) |
Recommendation: macerator for new SIMD work on stable Rust. It's a fork of pulp with type-generic operations and runtime multiversioning (SSE4.2 → AVX2 → AVX-512 dispatch). See macerator reference.
Watch list: fearless_simd (limited arch support — only NEON/WASM/SSE4.2), std::simd (nightly-only — check tracking issue for stabilization status).
PyO3 Upgrade Path
For Rust↔Python bindings, PyO3 has evolved significantly since 0.22. Always check the PyO3 changelog for the latest version:
| Version | Key Change |
|---|---|
| 0.22 | Bound<'_, T> API introduced (replaces GIL refs) |
| 0.23 | GIL ref removal complete, IntoPyObject trait |
| 0.24 | vectorcall support, performance improvements |
| 0.25+ | Free-threaded Python (3.13t) support, UniqueGilRef |
See PyO3 upgrade guide for migration patterns.
Reference Documents
- ast-grep-rust.md — AST-aware refactoring patterns
- cargo-hack.md — Feature flag testing
- cargo-mutants.md — Mutation testing
- cargo-nextest.md — Next-gen test runner
- cargo-pgo.md — Profile-Guided Optimization
- cargo-semver-checks.md — API compatibility
- cargo-wizard.md — Profile auto-configuration
- divan-and-criterion.md — Benchmarking comparison
- macerator-simd.md — Type-generic SIMD
- pyo3-upgrade-guide.md — PyO3 migration
- samply-profiling.md — Interactive profiling
Release Pipeline
A 4-phase release gate script is available at plugins/rust-tools/scripts/rust-release-check.sh. It consolidates all quality gates into a single executable that can be adapted to any Rust project.
Running
# Full pipeline (Phases 1-3)
./plugins/rust-tools/scripts/rust-release-check.sh
# Include nightly-only checks (Phase 4)
./plugins/rust-tools/scripts/rust-release-check.sh --nightly
# Skip test suite (Phases 1-2 only)
./plugins/rust-tools/scripts/rust-release-check.sh --skip-testsTo use as a mise task in your project, copy the script and add to .mise/tasks/:
cp plugins/rust-tools/scripts/rust-release-check.sh .mise/tasks/release-checkPhase Overview
| Phase | Name | Tools | Blocking | Notes |
|---|---|---|---|---|
| 1 | Fast Gates | fmt, clippy, audit, machete, geiger | Yes | Runs in parallel for speed |
| 2 | Deep Gates | deny, semver-checks, outdated | Mixed | outdated is advisory-only (never fails build) |
| 3 | Tests | nextest (or cargo test fallback) | Yes | Skippable with --skip-tests |
| 4 | Nightly-Only | udeps, hack | Yes | Opt-in via --nightly flag |
Phase 1 -- Fast Gates runs all tools in parallel using background processes. Each tool is checked for installation first; missing tools are skipped with a warning rather than failing.
Phase 2 -- Deep Gates runs sequentially. cargo deny requires a deny.toml to be present. cargo semver-checks only runs for library crates (detected via [lib] in Cargo.toml or src/lib.rs). cargo outdated is advisory -- it reports but never blocks.
Phase 3 -- Tests prefers cargo nextest run for speed but falls back to cargo test if nextest is not installed.
Phase 4 -- Nightly-Only requires the --nightly flag and a nightly toolchain. cargo +nightly udeps finds truly unused dependencies. cargo hack check --each-feature verifies every feature flag compiles independently.
Exit Codes
- 0 -- All blocking gates passed (advisory warnings are OK)
- 1 -- One or more blocking gates failed
The summary at the end reports total passes, failures, and advisory warnings.
Troubleshooting
| Problem | Solution |
|---|---|
ast-grep no matches | Check --lang rust flag; patterns must match AST nodes, not text |
samply permission denied | macOS: sudo samply record or disable SIP for dtrace |
cargo-pgo no speedup | Workload during profiling must be representative of real usage |
cargo-mutants too slow | Filter with --file or --regex; use -- --test-tool nextest |
divan vs criterion conflict | They can coexist — use separate bench targets in Cargo.toml |
macerator compile errors | Check minimum Rust version; requires SIMD target features |
cargo-nextest missing tests | Doc-tests not supported; use cargo test --doc separately |
cargo-hack OOM on powerset | Use --depth 2 to limit combinations |
Post-Execution Reflection
After this skill completes, reflect before closing the task:
0. Locate yourself. — Find this SKILL.md's canonical path before editing. 1. What failed? — Fix the instruction that caused it. 2. What worked better than expected? — Promote to recommended practice. 3. What drifted? — Fix any script, reference, or dependency that no longer matches reality. 4. Log it. — Evolution-log entry with trigger, fix, and evidence.
Do NOT defer. The next invocation inherits whatever you leave behind.
ast-grep for Rust
AST-aware code search and structural rewriting for Rust codebases. Unlike regex, ast-grep understands Rust syntax — it matches on the abstract syntax tree, not text patterns.
Installation
cargo install ast-grep
# or via npm
npm install -g @ast-grep/cliCore Concepts
Pattern Syntax
ast-grep patterns use metavariables to match AST nodes:
| Metavariable | Matches |
|---|---|
$X | Any single AST node |
$$$X | Zero or more AST nodes (variadic) |
$_ | Any single node (unnamed, no capture) |
Language Flag
Always specify --lang rust (or -l rust) — ast-grep supports multiple languages and needs to know the parser.
Common Rust Patterns
Error Handling
# Find all .unwrap() calls
ast-grep -p '$X.unwrap()' -l rust
# Find all .expect() calls
ast-grep -p '$X.expect($MSG)' -l rust
# Replace unwrap with expect
ast-grep -p '$X.unwrap()' -r '$X.expect("TODO: handle error")' -l rust
# Find unwrap_or with expensive default (should be unwrap_or_else)
ast-grep -p '$X.unwrap_or($DEFAULT)' -l rustUnsafe Code
# Find all unsafe blocks
ast-grep -p 'unsafe { $$$BODY }' -l rust
# Find unsafe fn declarations
ast-grep -p 'unsafe fn $NAME($$$ARGS) $BODY' -l rust
# Find unsafe impl blocks
ast-grep -p 'unsafe impl $TRAIT for $TYPE { $$$BODY }' -l rustPattern Matching Refactors
# Convert single-arm match to if-let
ast-grep -p 'match $X { $P => $E, _ => () }' -r 'if let $P = $X { $E }' -l rust
# Find match with single arm (potential if-let candidate)
ast-grep -p 'match $X { $P => $E, _ => $F }' -l rustDeprecated API Migration
# Replace deprecated try! with ?
ast-grep -p 'try!($X)' -r '$X?' -l rust
# Find manual Result handling (potential ? candidate)
ast-grep -p 'match $X { Ok($V) => $V, Err($E) => return Err($E) }' -l rustClone and Copy Patterns
# Find .clone() calls (potential unnecessary allocations)
ast-grep -p '$X.clone()' -l rust
# Find .to_string() on string literals
ast-grep -p '"$S".to_string()' -l rust
# Find String::from on literals (prefer .to_string() or .into())
ast-grep -p 'String::from("$S")' -l rustYAML Rule Files
For complex multi-rule transforms, create YAML rule files:
# rules/unwrap-to-expect.yml
id: unwrap-to-expect
language: rust
rule:
pattern: $X.unwrap()
not:
inside:
kind: test # Skip test functions
fix: $X.expect("TODO: handle None/Err")
message: "Replace .unwrap() with .expect() for better panic messages"
severity: warning# Run rules
ast-grep scan --rule rules/unwrap-to-expect.yml
# Run all rules in a directory
ast-grep scan --rule rules/Rule Composition
# rules/clippy-style.yml
id: manual-map
language: rust
rule:
pattern: |
match $X {
Some($V) => Some($E),
None => None,
}
fix: $X.map(|$V| $E)
message: "Use .map() instead of match on Option"Interactive Mode
# Interactive search with preview
ast-grep -p '$PATTERN' -l rust --interactive
# JSON output for scripting
ast-grep -p '$X.unwrap()' -l rust --jsonIntegration with CI
# .github/workflows/ast-grep.yml
- name: ast-grep lint
run: |
cargo install ast-grep
ast-grep scan --rule rules/ --error # Non-zero exit on findingsConfiguration File
Create sgconfig.yml at project root:
ruleDirs:
- rules/
testConfigs:
- testDir: rules/tests/Tips
- Whitespace insensitive: ast-grep ignores formatting differences
- Comment aware: Patterns skip comments in the AST
- Nested matching:
$$$BODYcaptures entire blocks including nested structures - Debugging patterns: Use
ast-grep parse <file> --lang rustto see the AST - Performance: ast-grep is very fast — it uses tree-sitter parsing, not compilation
Comparison with Other Tools
| Tool | AST-Aware | Rust-Specific | Rewrite | Speed |
|---|---|---|---|---|
| ast-grep | Yes (tree-sitter) | Multi-language | Yes | Very fast |
clippy | Yes (rustc) | Yes | Limited (suggestions) | Slow (full compile) |
semgrep | Yes | Multi-language | Yes | Fast |
grep/rg | No | No | No | Fastest |
ast-grep fills the gap between fast-but-dumb text search and slow-but-smart full compilation.
cargo-hack Extended Reference
Deep reference for feature flag powerset testing. Supplements the core cargo-hack reference with advanced patterns.
Feature Powerset Explained
Given a crate with features a, b, c, the powerset is every possible combination:
(none), a, b, c, a+b, a+c, b+c, a+b+cThat is 2^N combinations (8 for 3 features). For a crate with 10 features, that is 1024 runs. The --depth flag limits this to manageable subsets.
Depth Limiter
--depth N limits the maximum number of features enabled simultaneously:
| Depth | Combinations Tested | Runs (10 features) | Catches |
|---|---|---|---|
| 1 | Each feature alone + none + all | ~12 | Single-feature breakage |
| 2 | All pairs + depth 1 | ~57 | Pairwise conflicts |
| 3 | All triples + depth 2 | ~187 | Three-way interactions |
| (all) | Full powerset | 1024 | Everything |
Recommendation: --depth 2 is the sweet spot for most projects. It catches the majority of real-world feature conflicts (pairwise interactions) without combinatorial explosion.
# Depth 2 — practical default
cargo hack check --feature-powerset --depth 2 --no-dev-deps
# Depth 3 — thorough (use for releases)
cargo hack check --feature-powerset --depth 3 --no-dev-deps--each-feature vs --feature-powerset
| Mode | What it tests | When to use |
|---|---|---|
--each-feature | No features, each feature alone, all | Quick CI check, per-PR |
--feature-powerset | Every combination (bounded by depth) | Pre-release, thorough validation |
--each-feature is equivalent to --feature-powerset --depth 1 plus an all-features run.
# Quick check (CI, every PR)
cargo hack check --each-feature --no-dev-deps
# Thorough check (pre-release)
cargo hack check --feature-powerset --depth 2 --no-dev-depsGrouping Features
--group-features treats multiple features as a single unit, reducing the combinatorial space:
# "serde" and "serde_json" always go together — treat as one
cargo hack check --feature-powerset --depth 2 \
--group-features serde,serde_json
# Multiple groups (separated by --group-features flags)
cargo hack check --feature-powerset --depth 2 \
--group-features serde,serde_json \
--group-features async-std,async-traitUse grouping when:
- Features are always used together (e.g.,
serde+serde_json) - Features are mutually exclusive backends (group each backend's features)
- You want to reduce CI time by collapsing correlated features
Include/Exclude Specific Features
# Only test these features in the powerset
cargo hack check --feature-powerset --include-features a,b,c
# Skip features that need external system deps
cargo hack check --feature-powerset --skip ffi,gpu,system-openssl
# Combine: test only relevant features, skip problematic ones
cargo hack check --feature-powerset --depth 2 \
--include-features core,alloc,std \
--skip nightlyCI Sharding
For large projects, shard the powerset across multiple CI jobs:
GitHub Actions Matrix Sharding
jobs:
feature-check:
runs-on: ubuntu-latest
strategy:
matrix:
# Shard by depth level
check-type:
- name: "No features"
args: "--no-default-features"
- name: "Each feature"
args: "--each-feature --no-dev-deps"
- name: "Powerset depth 2"
args: "--feature-powerset --depth 2 --no-dev-deps"
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install cargo-hack
run: cargo install cargo-hack
- name: "${{ matrix.check-type.name }}"
run: cargo hack check ${{ matrix.check-type.args }}Workspace Sharding
For large workspaces, shard by package:
jobs:
feature-check:
runs-on: ubuntu-latest
strategy:
matrix:
package: [crate-a, crate-b, crate-c]
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install cargo-hack
run: cargo install cargo-hack
- name: Check ${{ matrix.package }}
run: cargo hack check --feature-powerset --depth 2 --no-dev-deps -p ${{ matrix.package }}Time-Based Sharding Strategy
# Fast tier (every PR) — ~2 min
fast-check:
runs-on: ubuntu-latest
steps:
- run: cargo hack check --each-feature --no-dev-deps
# Thorough tier (nightly/weekly) — ~15 min
thorough-check:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- run: cargo hack check --feature-powerset --depth 3 --no-dev-deps
- run: cargo hack test --each-featureAdvanced Patterns
MSRV Verification Across Features
# Verify MSRV holds for all feature combinations
cargo +1.70.0 hack check --feature-powerset --depth 2 --no-dev-depsDocumentation Build Verification
# Ensure docs build for every feature combination
cargo hack doc --each-feature --no-depsCombined with cargo-semver-checks
# Pre-publish: verify features don't break semver
cargo hack check --feature-powerset --depth 2 --no-dev-deps
cargo semver-checks check-releaseFeature Flag Debugging
When a specific combination fails, narrow it down:
# Test a specific feature combination
cargo hack check --features a,c --no-default-features
# Test with default features plus one
cargo hack check --features default,experimentalFlags Quick Reference
| Flag | Purpose |
|---|---|
--each-feature | Test each feature individually (+ none + all) |
--feature-powerset | Test all feature combinations |
--depth N | Max features enabled simultaneously in powerset |
--no-dev-deps | Skip dev-dependencies (faster check) |
--skip FEATURES | Comma-separated features to exclude |
--include-features | Only test these features in powerset |
--group-features | Treat listed features as a single unit |
--workspace | Check all workspace crates |
-p PACKAGE | Check specific package |
--no-default-features | Start powerset from zero features |
--version-range | Test across Rust compiler versions (MSRV) |
--clean-per-run | Clean target dir between runs (slower but avoids caching) |
Tips
--depth 2catches ~95% of real feature interaction bugs- Always use
--no-dev-depswithcheck(dev-deps are irrelevant for compilation checks) - Use
--group-featuresto collapse features that are always used together - Shard large workspaces by package in CI to keep job times under 10 minutes
- Run
--each-featureon every PR, save--feature-powersetfor nightly/pre-release - Combine with
cargo hack test --each-featureto catch runtime feature issues (not just compilation)
cargo-hack
Feature flag combination testing for Rust crates. Essential for library authors to verify that all feature combinations compile and pass tests.
Installation
cargo install cargo-hackWhy cargo-hack
Rust crates often expose feature flags, but testing only the default combination misses:
- Features that conflict with each other
- Features that depend on missing optional deps
- Code that compiles only with specific flag combinations
#[cfg(feature = "...")]blocks that are never tested
Core Commands
Test Every Feature Individually
# Check each feature alone (no default features + one at a time)
cargo hack check --each-feature
# Same but also run tests
cargo hack test --each-featureThis runs cargo check once per feature, plus once with no features and once with all features.
Feature Powerset Testing
# Test ALL feature combinations (2^n runs — use with care)
cargo hack check --feature-powerset
# Limit depth to avoid combinatorial explosion
cargo hack check --feature-powerset --depth 2
# Exclude dev-deps (faster, check-only)
cargo hack check --feature-powerset --no-dev-depsWarning: With N features, --feature-powerset runs 2^N times. Use --depth to cap:
--depth 1= each feature alone (~N runs)--depth 2= each pair (~N^2/2 runs)--depth 3= each triple (~N^3/6 runs)
Skip Specific Features
# Skip features that are known-incompatible
cargo hack check --each-feature --skip feature-a,feature-b
# Skip features that need external deps (e.g., system libs)
cargo hack check --each-feature --skip ffi,gpuWorkspace Support
# Check all crates in workspace
cargo hack check --each-feature --workspace
# Specific package
cargo hack check --each-feature -p my-crateCI Integration
GitHub Actions
- name: Feature flag testing
run: |
cargo install cargo-hack
cargo hack check --feature-powerset --depth 2 --no-dev-depsRecommended CI Matrix
jobs:
feature-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install cargo-hack
run: cargo install cargo-hack
- name: Check feature powerset
run: cargo hack check --feature-powerset --depth 2 --no-dev-deps
- name: Test each feature
run: cargo hack test --each-featureCommon Patterns
Library Crate Validation
# Full validation before publish
cargo hack check --feature-powerset --no-dev-deps
cargo hack test --each-feature
cargo hack doc --each-feature --no-depsMSRV (Minimum Supported Rust Version) Check
# Check MSRV with all feature combinations
cargo +1.70.0 hack check --feature-powerset --depth 2Conditional Compilation Audit
# Find features that break compilation when alone
cargo hack check --each-feature 2>&1 | grep "error"Flags Reference
| Flag | Purpose |
|---|---|
--each-feature | Test each feature individually |
--feature-powerset | Test all feature combinations |
--depth N | Limit powerset depth |
--no-dev-deps | Skip dev-dependencies |
--skip FEATURES | Comma-separated features to skip |
--workspace | Check all workspace crates |
--include-features | Only test these features |
--group-features | Treat listed features as one group |
--version-range | Test across Rust versions (MSRV) |
Tips
- Start with
--depth 2— it catches most real issues without combinatorial explosion - Use
--no-dev-depsforcheck(faster) and full deps fortest - Combine with
cargo-semver-checksfor pre-publish validation - Run
cargo hackin CI on every PR for library crates
cargo-mutants
Mutation testing for Rust: automatically modifies your source code and checks whether your tests catch the changes. Finds weak spots in your test suite.
Installation
cargo install cargo-mutantsHow It Works
1. cargo-mutants generates mutants — small changes to your source code 2. For each mutant, it runs your test suite 3. If tests still pass with the mutation → missed (your tests are weak here) 4. If tests fail → caught (your tests are good here)
Mutation Types
| Mutation | Example | What It Tests |
|---|---|---|
| Replace return value | fn foo() -> bool returns true → false | Return value assertions |
| Replace with default | fn foo() -> Vec<T> returns vec![] | Non-empty result checks |
| Remove function body | fn process() { ... } → fn process() {} | Side effect testing |
| Negate condition | if x > 0 → if x <= 0 | Branch coverage |
| Replace binary op | a + b → a - b | Arithmetic correctness |
Basic Usage
# Run mutation testing on entire crate
cargo mutants
# Dry run — show what mutations would be generated (no testing)
cargo mutants --list
# Count mutations
cargo mutants --list | wc -lFiltering
# Filter to specific files
cargo mutants --file src/parser.rs
cargo mutants --file "src/lib.rs" --file "src/core.rs"
# Filter by function name regex
cargo mutants --regex "parse_.*"
cargo mutants --regex "^(encode|decode)"
# Exclude specific functions
cargo mutants --exclude "test_.*"
cargo mutants --exclude "Debug|Display"
# Skip functions returning specific types
cargo mutants --skip-calls-to "log::*,tracing::*"Using cargo-nextest (Faster)
# Use nextest as the test runner (recommended — much faster)
cargo mutants -- --test-tool nextest
# With nextest profile
cargo mutants -- --test-tool nextest --profile ciResult Categories
| Category | Meaning | Action |
|---|---|---|
| caught | Tests detected the mutation | Good — tests are effective |
| missed | Tests still passed with mutation | Bad — add/improve tests |
| timeout | Tests took too long with mutation | Usually OK (infinite loop from mutation) |
| unviable | Mutated code doesn't compile | Neutral — type system caught it |
Reading Results
# Results directory
ls mutants.out/
# Key files
cat mutants.out/missed.txt # Mutations your tests didn't catch
cat mutants.out/caught.txt # Mutations your tests caught
cat mutants.out/timeout.txt # Mutations that caused timeouts
cat mutants.out/unviable.txt # Mutations that didn't compile
# Detailed log
cat mutants.out/outcomes.json # Machine-readable resultsConfiguration
In Cargo.toml
[package.metadata.cargo-mutants]
# Skip functions that are hard to test
exclude_re = ["Debug", "Display", "Default"]
# Skip calls to logging/tracing (mutations here are noise)
skip_calls_to = ["log::.*", "tracing::.*", "println"]
# Timeout multiplier (default: 5x normal test time)
timeout_multiplier = 3.0In .cargo/mutants.toml
# Equivalent to package.metadata but in separate file
exclude_re = ["Debug", "Display"]
skip_calls_to = ["log::.*"]
timeout_multiplier = 3.0CI Integration
# GitHub Actions
- name: Mutation testing
run: |
cargo install cargo-mutants cargo-nextest
cargo mutants -- --test-tool nextest --timeout 300
- name: Check for missed mutants
run: |
if [ -s mutants.out/missed.txt ]; then
echo "::warning::Missed mutants found"
cat mutants.out/missed.txt
fiParallelism
# Run mutations in parallel (default: number of CPUs)
cargo mutants --jobs 4
# Incremental: only test mutations in changed files
cargo mutants --in-diff git diff mainInterpreting Results
High missed count in a file?
The tests for that module are likely:
1. Missing edge cases — add targeted tests 2. Only testing happy path — add error/boundary tests 3. Testing implementation, not behavior — refactor tests
All caught?
Your test suite is strong for that code. Focus mutation testing on:
- Recently changed code
- Critical paths (payment, auth, data integrity)
- Complex logic with many branches
Tips
- Start with
--fileon critical modules, not the whole crate --regexis great for focusing on specific subsystems- Combine with
cargo-nextestfor 2-3x faster mutation runs unviablemutations are free wins — Rust's type system is your friend- Run
--listfirst to estimate how long the full run will take - Typical: 5-20 seconds per mutation (depends on test suite speed)
cargo-nextest
Next-generation Rust test runner. Runs each test in its own process (vs cargo test's shared-process model), giving 3x faster execution, better output, flaky test detection, and JUnit XML reports.
Installation
cargo install cargo-nextestWhy cargo-nextest
| Feature | cargo test | cargo nextest |
|---|---|---|
| Execution model | Shared process | Process-per-test |
| Speed | Baseline | ~3x faster (parallel) |
| Flaky detection | No | Built-in retries |
| Output | Interleaved | Clean, per-test |
| JUnit XML | No | Built-in |
| Test filtering | Basic | Regex + filter expressions |
| Timeouts | No | Per-test and per-suite |
| Doc tests | Yes | No (use cargo test --doc) |
Basic Usage
# Run all tests
cargo nextest run
# Run specific test
cargo nextest run test_name
# Run tests matching regex
cargo nextest run -E 'test(parse_)'
# Run tests in specific package (workspace)
cargo nextest run -p my-crate
# List tests without running
cargo nextest listConfiguration
Config file: .config/nextest.toml (at project root)
[store]
# Directory for nextest artifacts
dir = "target/nextest"
[profile.default]
# Retry failed tests (catches flaky tests)
retries = 0
# Fail fast — stop on first failure
fail-fast = true
# Test timeout
slow-timeout = { period = "60s", terminate-after = 2 }
# Number of test threads
test-threads = "num-cpus"
[profile.ci]
# CI profile — more retries, JUnit output
retries = 2
fail-fast = false
slow-timeout = { period = "120s", terminate-after = 3 }
[profile.ci.junit]
path = "target/nextest/ci/junit.xml"Filter Expressions
nextest supports powerful filter expressions:
# Tests matching name
cargo nextest run -E 'test(my_test)'
# Tests in specific package
cargo nextest run -E 'package(my-crate)'
# Tests in specific binary
cargo nextest run -E 'binary(my-crate::bin/my-binary)'
# Combine with boolean operators
cargo nextest run -E 'test(parse_) & package(my-crate)'
cargo nextest run -E 'test(parse_) | test(lex_)'
cargo nextest run -E 'not test(slow_)'
# Tests that depend on specific binary
cargo nextest run -E 'deps(my-crate)'Profiles
# Use CI profile
cargo nextest run --profile ci
# Override retries
cargo nextest run --retries 3
# Override thread count
cargo nextest run --test-threads 2Flaky Test Detection
# Retry failed tests up to 3 times
cargo nextest run --retries 3
# In config:
[profile.default]
retries = { backoff = "exponential", count = 3, delay = "1s", max-delay = "10s" }When a test fails then passes on retry, nextest marks it as flaky in the output.
JUnit XML Output
# Generate JUnit XML (for CI systems)
cargo nextest run --profile ci
# Custom output path
cargo nextest run --profile ci --message-format libtest-jsonConfigure in .config/nextest.toml:
[profile.ci.junit]
path = "target/nextest/ci/junit.xml"
report-name = "my-project-tests"Partitioning (CI Sharding)
# Shard tests across CI machines
cargo nextest run --partition count:1/3 # Machine 1 of 3
cargo nextest run --partition count:2/3 # Machine 2 of 3
cargo nextest run --partition count:3/3 # Machine 3 of 3Archive for CI
# Create test archive (on build machine)
cargo nextest archive --archive-file tests.tar.zst
# Run from archive (on test machine — no Rust toolchain needed)
cargo nextest run --archive-file tests.tar.zstDebugging
# Run single test with output visible
cargo nextest run test_name --no-capture
# Run single test with debugger
cargo nextest run test_name -- --nocapture
# Show slow tests
cargo nextest run --status-level slowIntegration with cargo-mutants
# Use nextest as the test runner for mutation testing
cargo mutants -- --test-tool nextestTips
- Doc tests: nextest doesn't support them — run
cargo test --docseparately - Test isolation: Process-per-test means tests can't interfere with each other
- Speed: The process-per-test model enables better parallelism
- Config location:
.config/nextest.toml(not.cargo/) - Workspace: Works transparently with Cargo workspaces
- Pre-built binaries: Available via
cargo-binstall cargo-nextest(faster install)
Migration from cargo test
1. Install: cargo install cargo-nextest 2. Create .config/nextest.toml with profiles 3. Replace cargo test → cargo nextest run in scripts/CI 4. Keep cargo test --doc for doc-tests 5. Add --retries 2 in CI for flaky detection
cargo-pgo
Profile-Guided Optimization (PGO) and BOLT post-link optimization for Rust binaries. Automates the multi-phase PGO workflow that typically gives 10-20% speedup on CPU-bound code.
Installation
cargo install cargo-pgo
# For BOLT support (Linux only)
cargo install cargo-pgo --features boltWhat is PGO?
PGO is a compiler optimization technique:
1. Instrument: Compile with profiling instrumentation 2. Profile: Run the instrumented binary with representative workload 3. Optimize: Recompile using the collected profile data
The compiler uses profile data to make better decisions about:
- Function inlining
- Branch prediction hints
- Code layout (hot/cold splitting)
- Loop unrolling decisions
Three-Phase Workflow
Phase 1: Instrument
# Build instrumented binary
cargo pgo build
# Binary is at target/release/<name> (with instrumentation)Phase 2: Collect Profiles
# Run with REPRESENTATIVE workload
# The workload MUST reflect real usage — this is the critical step
./target/release/my-binary < typical_input.txt
./target/release/my-binary --benchmark real-data.json
# Multiple runs accumulate profile data (they merge)
./target/release/my-binary < input1.txt
./target/release/my-binary < input2.txt
# Profile data is written to target/pgo-profiles/Critical: The profiling workload must represent real usage. If you profile with synthetic data, optimizations target the wrong code paths.
Phase 3: Optimize
# Build with collected profiles
cargo pgo optimize
# The optimized binary is at target/release/<name>BOLT Post-Link Optimization (Linux Only)
BOLT reorganizes the binary after linking for better instruction cache utilization:
# Phase 1: Build with BOLT instrumentation
cargo pgo bolt build
# Phase 2: Run with workload
./target/release/my-binary < typical_input.txt
# Phase 3: Optimize with BOLT
cargo pgo bolt optimizeCombined PGO + BOLT
# PGO first, then BOLT on top
cargo pgo build
./target/release/my-binary < workload.txt
cargo pgo optimize
# Now BOLT on the PGO-optimized binary
cargo pgo bolt build
./target/release/my-binary < workload.txt
cargo pgo bolt optimizeWhen PGO Helps
| Scenario | Expected Gain | Why |
|---|---|---|
| CPU-bound parsers | 10-20% | Branch prediction, inlining |
| Compilers/interpreters | 15-25% | Hot loop optimization |
| Crypto/hashing | 5-10% | Code layout optimization |
| I/O-bound code | Minimal | Bottleneck isn't CPU |
| Short-lived CLIs | Minimal | Startup-dominated |
Combining with cargo-wizard
# Step 1: Use cargo-wizard for profile settings
cargo wizard # Choose "fast-runtime"
# Step 2: Then layer PGO on top
cargo pgo build
# ... run workload ...
cargo pgo optimizecargo-wizard sets Cargo profile options (LTO, codegen-units), while cargo-pgo adds profile-guided optimizations. They complement each other.
CI Integration
# PGO in CI (for release builds)
- name: PGO Build
run: |
cargo install cargo-pgo
cargo pgo build
./target/release/my-binary --bench # Representative workload
cargo pgo optimize
- name: Upload optimized binary
uses: actions/upload-artifact@v4
with:
name: my-binary-pgo
path: target/release/my-binaryFlags Reference
| Command | Purpose |
|---|---|
cargo pgo build | Build instrumented binary |
cargo pgo optimize | Build with collected profiles |
cargo pgo test | Run tests with instrumented binary |
cargo pgo bench | Run benchmarks with instrumented binary |
cargo pgo bolt build | Build BOLT-instrumented binary |
cargo pgo bolt optimize | Apply BOLT optimization |
cargo pgo info | Show PGO profile info |
Tips
- Profile quality matters more than quantity: One representative run > 100 synthetic runs
- Profile data location:
target/pgo-profiles/(auto-managed by cargo-pgo) - LTO + PGO: Enable LTO in your Cargo profile for maximum benefit
- Benchmarking PGO: Compare before/after with
hyperfineor divan - BOLT: Linux-only, requires LLVM BOLT (
llvm-boltbinary) - Incremental: PGO profiles are invalidated by code changes — re-profile after changes
- Author: Kobzol (major Rust contributor, works on rustc performance)
cargo-semver-checks
Lint your Rust crate's API for semver violations before publishing. Catches accidental breaking changes with hundreds of built-in lints (growing with each release).
Installation
cargo install cargo-semver-checksWhy cargo-semver-checks
Publishing a crate with accidental breaking changes (without a major version bump) causes downstream build failures. cargo-semver-checks catches these before cargo publish:
- Function signature changes
- Removed public items
- Changed trait requirements
- Type alias changes
- Struct field visibility changes
- And many more categories (run
cargo semver-checks --list-lintsfor the full list)
Basic Usage
# Check against last published version on crates.io
cargo semver-checks check-release
# Check against specific baseline version
cargo semver-checks check-release --baseline-version <baseline>
# Check against a git revision
cargo semver-checks check-release --baseline-rev <tag-or-sha>
# Workspace mode
cargo semver-checks check-release --workspace
# Specific package in workspace
cargo semver-checks check-release -p my-crateCommon Violations
| Violation | What Happened | Semver Impact |
|---|---|---|
function_missing | Public function removed | Major |
function_parameter_count_changed | Param added/removed | Major |
struct_missing | Public struct removed | Major |
struct_pub_field_missing | Public field removed | Major |
enum_variant_missing | Enum variant removed | Major |
trait_method_missing | Required method removed | Major |
method_parameter_count_changed | Method params changed | Major |
type_changed_kind | e.g., struct → enum | Major |
function_must_use_added | #[must_use] added | Minor (allowed) |
inherent_method_must_use_added | #[must_use] on method | Minor (allowed) |
Configuration
In Cargo.toml
[package.metadata.cargo-semver-checks]
# Lint-level overrides
[package.metadata.cargo-semver-checks.lints]
function_missing = "allow" # Override specific lintCLI Overrides
# Allow specific violations
cargo semver-checks check-release --allow function_missing
# Deny specific additions (stricter)
cargo semver-checks check-release --deny function_must_use_addedPre-Publish Workflow
# Full pre-publish check
cargo semver-checks check-release && \
cargo test && \
cargo doc --no-deps && \
echo "Ready to publish!"CI Integration
# GitHub Actions
- name: Semver check
run: |
cargo install cargo-semver-checks
cargo semver-checks check-release
# Or use the official action
- uses: obi1kenobi/cargo-semver-checks-action@v2Cargo Integration Status
cargo-semver-checks has an approved RFC for integration into Cargo itself. Until that lands, use it as a standalone tool. The lints and behavior will carry over.
Custom Lints
cargo-semver-checks supports custom lint definitions using a Trustfall query language:
# List all available lints
cargo semver-checks --list-lints
# Show details for a specific lint
cargo semver-checks --explain function_missingTips
- Run before every publish: Add to your pre-publish checklist
- Baseline version: defaults to latest on crates.io; use
--baseline-versionfor specific comparisons - False positives: Rare but possible — use lint-level overrides
- Workspace crates: Use
--workspaceto check all public crates - Speed: Much faster than a full compile — uses rustdoc JSON output
- Lint coverage: Comprehensive and growing with each release — run
--list-lintsto check - Combines with:
cargo-hack(test all features) andcargo-deny(full audit)
cargo-wizard
Auto-configure Cargo profiles for optimal compile time, runtime performance, or binary size. Endorsed by the Cargo team. Most LLMs don't know this tool exists.
Installation
cargo install cargo-wizardWhy cargo-wizard
Cargo has many profile settings (opt-level, lto, codegen-units, strip, panic, debug) that interact in non-obvious ways. cargo-wizard provides opinionated templates that configure all of them correctly for a specific goal.
Usage
# Interactive mode — choose optimization goal
cargo wizardThis presents three templates:
Template 1: Fast Compile Time
Minimizes build time for development iteration:
# What cargo-wizard sets in Cargo.toml:
[profile.dev]
opt-level = 0
debug = "line-tables-only"
incremental = true
codegen-units = 256Template 2: Fast Runtime Performance
Maximizes execution speed for release builds:
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
strip = "debuginfo"Template 3: Minimum Binary Size
Minimizes the output binary size:
[profile.release]
opt-level = "z"
lto = "fat"
codegen-units = 1
panic = "abort"
strip = trueHow It Works
1. Asks which optimization goal you want 2. Asks which profile to modify (dev, release, or custom) 3. Writes the appropriate settings to Cargo.toml 4. Shows what changed
Profile Settings Explained
| Setting | Fast Compile | Fast Runtime | Min Size |
|---|---|---|---|
opt-level | 0 | 3 | "z" |
lto | "off" | "fat" | "fat" |
codegen-units | 256 | 1 | 1 |
panic | "unwind" | "abort" | "abort" |
strip | false | "debuginfo" | true |
debug | "line-tables-only" | false | false |
incremental | true | false | false |
Key Trade-offs
- `lto = "fat"`: Enables Link-Time Optimization across all crates — slower compile, faster/smaller binary
- `codegen-units = 1`: Single compilation unit — slower compile, better optimization
- `panic = "abort"`: No unwinding — smaller binary, but no catch_unwind
- `strip = true`: Removes all symbols — smallest binary, but no debugging
Combining with Other Tools
With cargo-pgo
# Step 1: Set up profile with cargo-wizard
cargo wizard # Choose "fast-runtime"
# Step 2: Layer PGO on top for additional 10-20%
cargo pgo build
./target/release/my-binary < workload.txt
cargo pgo optimizeWith samply
# For profiling, you want release speed + debug info
# Manually adjust after cargo-wizard:
[profile.release]
opt-level = 3
debug = true # Keep debug info for profiler
strip = false # Don't strip symbolsCustom Profiles
cargo-wizard can also configure custom profiles:
# Create a "profiling" profile
cargo wizard # Select custom profile name# Result:
[profile.profiling]
inherits = "release"
debug = true
strip = falseTips
- Run once per project: Settings persist in
Cargo.toml - Combine templates: Use "fast-compile" for dev, "fast-runtime" for release
- Review changes: cargo-wizard shows the diff — review before accepting
- Author: Kobzol (same author as cargo-pgo, major Rust/rustc contributor)
- Cargo team endorsed: Featured in Cargo team discussions
- Idempotent: Running again with same choices produces same output
Benchmarking: divan and Criterion
Two leading Rust benchmarking frameworks compared. divan offers a simpler attribute-based API; Criterion provides statistical analysis and HTML reports.
divan
Installation
Add to Cargo.toml:
[dev-dependencies]
divan = "<version>" # See https://crates.io/crates/divan
[[bench]]
name = "my_benchmarks"
harness = falseBasic Usage
// benches/my_benchmarks.rs
fn main() {
divan::main();
}
#[divan::bench]
fn simple_bench() {
// Code to benchmark (return value is black-boxed automatically)
std::hint::black_box(fibonacci(20));
}
#[divan::bench]
fn bench_with_bencher(bencher: divan::Bencher) {
// Setup outside the timing loop
let data = prepare_data();
bencher.bench(|| {
process(&data)
});
}Generic Benchmarks
divan's killer feature — benchmark across multiple types with one function:
#[divan::bench(types = [Vec<u8>, Vec<u16>, Vec<u32>, Vec<u64>])]
fn bench_sort<T: Ord + Default + Clone>(bencher: divan::Bencher) {
let data: Vec<T> = generate_data();
bencher
.with_inputs(|| data.clone())
.bench_values(|mut v| v.sort());
}Allocation Profiling
Built-in AllocProfiler — no external tools needed:
#[global_allocator]
static ALLOC: divan::AllocProfiler = divan::AllocProfiler::system();
fn main() {
divan::main();
}
#[divan::bench]
fn bench_allocations() {
// divan automatically reports allocation count and bytes
let v: Vec<i32> = (0..1000).collect();
std::hint::black_box(v);
}Output includes: allocs/iter, bytes/iter, and allocation patterns.
Parameterized Benchmarks
#[divan::bench(args = [10, 100, 1000, 10000])]
fn bench_fibonacci(n: u64) -> u64 {
fibonacci(n)
}
// Multiple parameter axes
#[divan::bench(
types = [Vec<u8>, Vec<u32>],
args = [100, 1000, 10000],
)]
fn bench_sort<T: Ord + Default>(bencher: divan::Bencher, len: usize) {
bencher
.with_inputs(|| generate_vec::<T>(len))
.bench_values(|mut v| v.sort());
}Running
cargo bench
# Filter specific benchmarks
cargo bench -- bench_sort
# With specific sample count
cargo bench -- --sample-count 100Criterion
Installation
Add to Cargo.toml:
[dev-dependencies]
criterion = { version = "<version>", features = ["html_reports"] } # See https://crates.io/crates/criterion
[[bench]]
name = "my_benchmarks"
harness = falseBasic Usage
// benches/my_benchmarks.rs
use criterion::{criterion_group, criterion_main, Criterion, black_box};
fn bench_fibonacci(c: &mut Criterion) {
c.bench_function("fibonacci_20", |b| {
b.iter(|| fibonacci(black_box(20)));
});
}
criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);Parameterized Benchmarks
fn bench_sort_sizes(c: &mut Criterion) {
let mut group = c.benchmark_group("sort");
for size in [100, 1000, 10000] {
group.bench_with_input(
BenchmarkId::from_parameter(size),
&size,
|b, &size| {
let data: Vec<i32> = (0..size).collect();
b.iter(|| {
let mut v = data.clone();
v.sort();
v
});
},
);
}
group.finish();
}Throughput Measurement
fn bench_throughput(c: &mut Criterion) {
let mut group = c.benchmark_group("parse");
let input = "large input string...";
group.throughput(Throughput::Bytes(input.len() as u64));
group.bench_function("parse", |b| {
b.iter(|| parse(black_box(input)));
});
group.finish();
}Statistical Analysis
Criterion automatically provides:
- Confidence intervals
- Change detection (vs previous run)
- Outlier detection
- Linear regression for throughput
HTML Reports
cargo bench
# Reports generated at target/criterion/report/index.htmlRunning
cargo bench
# Filter specific benchmarks
cargo bench -- sort
# Save baseline
cargo bench -- --save-baseline before_change
# Compare against baseline
cargo bench -- --baseline before_changeComparison
| Feature | divan | Criterion |
|---|---|---|
| API | #[divan::bench] attribute | criterion_group! macro |
| Setup complexity | Minimal | Moderate (macros, groups) |
| Generic benchmarks | Built-in types = [...] | Manual with macros |
| Allocation tracking | Built-in AllocProfiler | External (dhat, etc.) |
| Statistical analysis | Basic | Comprehensive (confidence intervals) |
| Reports | Terminal (colored) | HTML + Gnuplot |
| Throughput | Basic | Built-in Throughput type |
| Baselines/comparison | No | Yes (--save-baseline) |
| CI integration | CodSpeed (native) | CodSpeed + criterion-compare |
| Maintenance | Maintained (check crates.io) | Active (criterion-rs organization) |
CodSpeed CI Integration
Both frameworks support CodSpeed for continuous benchmarking in CI:
# GitHub Actions with CodSpeed
- uses: CodSpeedHQ/action@v3
with:
run: cargo bench # Works with both divan and criterion
token: ${{ secrets.CODSPEED_TOKEN }}Recommendation
- New projects: Start with divan (simpler API, generic benchmarks, allocation profiling)
- Existing Criterion users: Stay with Criterion (active maintenance, HTML reports)
- Need statistical rigor: Criterion (confidence intervals, change detection)
- Need allocation profiling: divan (built-in, zero config)
- Library crates: Consider both — divan for dev, Criterion for published benchmarks
Evolution Log: rust-sota-arsenal
2026-03-01 — Initial Creation
- Created plugin with 11 reference documents covering 15 tools
- Categories: Refactoring, Performance, Benchmarking, Testing, SIMD, Python Bindings
- All tools web-verified for maintenance status and latest versions
- Tools confirmed SOTA: ast-grep, cargo-semver-checks, samply, cargo-pgo, cargo-wizard, divan, Criterion, cargo-nextest, cargo-mutants, cargo-hack, macerator
- Not included (superseded/too early): fearless_simd, simdeez, std::simd (nightly), Mutagen
macerator: Type-Generic SIMD
Type-generic SIMD operations with runtime multiversioning on stable Rust. A fork of pulp that adds type-generic operations and improved architecture support.
Installation
Add to Cargo.toml:
[dependencies]
macerator = "<version>" # See https://crates.io/crates/maceratorWhy macerator
The Rust SIMD landscape:
| Crate | Stable | Type-Generic | Multiversioning | Status |
|---|---|---|---|---|
| macerator | Yes | Yes | Yes | Active |
wide | Yes | No (concrete) | No | Active |
pulp | Yes | Yes | Yes | Superseded |
std::simd | Nightly | Yes | No | Nightly-only (tracking issue: rust-lang/rust#86656) |
packed_simd | Nightly | Yes | No | Deprecated |
macerator is the only option that provides all three: stable Rust, type-generic operations, and runtime multiversioning.
Core Concepts
Type-Generic Operations
Write SIMD code once, works across f32, f64, i32, u64, etc.:
use macerator::{SimdFor, Simd};
fn dot_product<T: SimdFor>(a: &[T], b: &[T]) -> T
where
T: std::ops::Mul<Output = T> + std::ops::Add<Output = T> + Default + Copy,
{
// This function works for f32, f64, i32, etc.
// macerator handles the SIMD width automatically
let simd = Simd::new();
simd.vectorize(|| {
a.iter()
.zip(b.iter())
.map(|(&x, &y)| x * y)
.fold(T::default(), |acc, v| acc + v)
})
}Runtime Multiversioning
macerator compiles multiple versions of your SIMD code and selects the best at runtime based on CPU features:
use macerator::Simd;
fn process(data: &mut [f32]) {
let simd = Simd::new(); // Detects CPU features at runtime
// Dispatches to best available:
// - AVX-512 on supported CPUs
// - AVX2 on most modern x86_64
// - SSE4.2 on older x86_64
// - NEON on ARM
simd.vectorize(|| {
for x in data.iter_mut() {
*x = x.sqrt();
}
});
}The dispatch happens once (at Simd::new()) — not per-operation.
Architecture Support
| Architecture | Instruction Sets |
|---|---|
| x86_64 | SSE4.2, AVX2, AVX-512 |
| aarch64 | NEON |
| wasm32 | SIMD128 |
Migration from pulp
macerator is a fork of pulp. Migration is mostly renaming:
// Before (pulp):
use pulp::Simd;
let simd = Simd::new();
// After (macerator):
use macerator::Simd;
let simd = Simd::new();Key differences from pulp:
- Type-generic operations (pulp required concrete types)
- Better ARM/NEON support
- Continued maintenance (pulp is no longer updated)
Patterns
Vectorized Map
use macerator::Simd;
fn scale(data: &mut [f32], factor: f32) {
let simd = Simd::new();
simd.vectorize(|| {
for x in data.iter_mut() {
*x *= factor;
}
});
}Vectorized Reduction
use macerator::Simd;
fn sum(data: &[f32]) -> f32 {
let simd = Simd::new();
simd.vectorize(|| {
data.iter().copied().sum()
})
}Conditional SIMD
use macerator::Simd;
fn clamp(data: &mut [f32], min: f32, max: f32) {
let simd = Simd::new();
simd.vectorize(|| {
for x in data.iter_mut() {
*x = x.max(min).min(max);
}
});
}Comparison with wide
wide provides concrete SIMD types (f32x4, f32x8), while macerator provides type-generic operations:
// wide: concrete types, manual width selection
use wide::f32x8;
let a = f32x8::from([1.0; 8]);
let b = f32x8::from([2.0; 8]);
let c = a + b;
// macerator: type-generic, automatic width
use macerator::Simd;
let simd = Simd::new();
simd.vectorize(|| {
// Works on any numeric type, auto-selects width
});Use wide when: You need explicit control over SIMD width and types. Use macerator when: You want portable, type-generic SIMD with automatic dispatch.
Comparison with std::simd
std::simd (nightly-only) provides similar type-generic operations:
// std::simd (nightly only):
#![feature(portable_simd)]
use std::simd::f32x4;
// macerator (stable):
use macerator::Simd;Use std::simd when: You're on nightly and want stdlib support. Use macerator when: You need stable Rust (which is most projects).
Watch List
- `fearless_simd`: Limited arch support (only NEON/WASM/SSE4.2) — check crates.io for updates
- `std::simd` stabilization: Track rust-lang/rust#86656 for stabilization progress
- `simdeez`: Low adoption — check crates.io download counts before adopting
Tips
- Start with `Simd::new()`: Let macerator detect the best ISA
- Profile first: Use samply to confirm SIMD is your bottleneck before optimizing
- Alignment: macerator handles alignment internally — no manual alignment needed
- Fallback: macerator always provides a scalar fallback if no SIMD is available
- Testing: SIMD code should produce identical results to scalar — test both paths
- Benchmarking: Use divan or Criterion to measure actual speedup
PyO3 Upgrade Guide: 0.22+
Migration guide for PyO3 Rust↔Python bindings. PyO3 has evolved significantly — the API surface changed substantially starting from 0.22. Always check the PyO3 changelog for the latest version.
Web-verify first: Before planning a PyO3 migration, check the actual latest versions:
>
- WebFetch: https://crates.io/api/v1/crates/pyo3 — get latest PyO3 version- WebFetch: https://crates.io/api/v1/crates/pyo3-arrow — if using Arrow bindings- Check that downstream crates (pyo3-arrow, pyo3-polars, etc.) support the target PyO3 version before upgrading
Version Overview
| Version | Key Change |
|---|---|
| 0.22 | Bound<'py, T> API introduced (replaces GIL refs) |
| 0.23 | GIL ref removal complete, IntoPyObject trait |
| 0.24 | vectorcall support, performance improvements |
| 0.25+ | Free-threaded Python (3.13t) support, UniqueGilRef |
The Big Change: Bound API (0.22)
Before (GIL References — Deprecated)
// OLD: Using &PyAny, &PyDict, etc. (GIL references)
use pyo3::prelude::*;
use pyo3::types::PyDict;
#[pyfunction]
fn old_style(py: Python<'_>, dict: &PyDict) -> PyResult<()> {
let value = dict.get_item("key")?;
Ok(())
}After (Bound API — Current)
// NEW: Using Bound<'py, PyAny>, Bound<'py, PyDict>, etc.
use pyo3::prelude::*;
use pyo3::types::PyDict;
#[pyfunction]
fn new_style(dict: &Bound<'_, PyDict>) -> PyResult<()> {
let value = dict.get_item("key")?;
Ok(())
}Why the Change
- GIL references (
&PyAny) tied the reference to the GIL lifetime implicitly Bound<'py, T>makes the GIL lifetime explicit- Required for free-threaded Python (3.13t) support
- Better memory safety guarantees
Migration Patterns
Pattern 1: Function Arguments
// Before:
fn process(obj: &PyAny) -> PyResult<()> { ... }
// After:
fn process(obj: &Bound<'_, PyAny>) -> PyResult<()> { ... }Pattern 2: Return Types
// Before:
fn create_dict(py: Python<'_>) -> PyResult<&PyDict> {
let dict = PyDict::new(py);
Ok(dict)
}
// After:
fn create_dict(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> {
let dict = PyDict::new(py);
Ok(dict)
}Pattern 3: Extracting Values
// Before:
let value: i64 = obj.extract()?;
// After (same syntax, works on Bound):
let value: i64 = obj.extract()?;Pattern 4: Creating Python Objects
// Before:
let list = PyList::new(py, &[1, 2, 3]);
// After:
let list = PyList::new(py, [1, 2, 3])?; // Note: now returns ResultIntoPyObject Trait (0.23)
Replaces IntoPy<PyObject> and ToPyObject:
// Before:
impl IntoPy<PyObject> for MyType {
fn into_py(self, py: Python<'_>) -> PyObject {
self.value.into_py(py)
}
}
// After:
impl<'py> IntoPyObject<'py> for MyType {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.value.into_pyobject(py)
}
}For simple cases, derive macros handle this automatically:
#[pyclass]
#[derive(Clone)]
struct MyType {
value: i64,
}
// IntoPyObject is auto-derived for #[pyclass] typesVectorcall Support (0.24)
Faster Python function calls using the vectorcall protocol:
// Automatic for #[pyfunction] and #[pymethods]
// No code changes needed — PyO3 uses vectorcall internally when availablePerformance improvement: ~10-30% faster for frequently-called functions.
Free-Threaded Python (0.25+)
Python 3.14t (free-threaded, no GIL) support:
// Check if running free-threaded at runtime
if pyo3::cfg!(Py_GIL_DISABLED) {
// Running on free-threaded Python
}Key considerations for free-threaded:
Bound<'py, T>is required (GIL refs won't work)- Shared mutable state needs explicit synchronization
#[pyclass]types should beSend + Syncwhen possible
Migration Checklist
1. Update Cargo.toml: Change PyO3 version 2. Replace GIL references: &PyAny → &Bound<'_, PyAny>, etc. 3. Update return types: &PyDict → Bound<'_, PyDict> 4. Handle new Result types: PyList::new() now returns Result 5. Update IntoPy: Replace IntoPy<PyObject> with IntoPyObject 6. Test with maturin: maturin develop to verify compilation 7. Test Python side: Run Python tests to verify behavior
Build Tools
# Development build (fast iteration)
maturin develop --release
# Build wheel for distribution
maturin build --release
# Build and install
pip install .Tips
- Incremental migration: PyO3 0.22-0.23 supports both old and new APIs — migrate gradually
- Deprecation warnings: Enable them to find old API usage:
RUSTFLAGS="-W deprecated" - maturin: Preferred build tool for PyO3 projects
- Python version: Test against Python 3.9+ (PyO3 minimum)
- Changelog: Always check the PyO3 changelog for version-specific notes
- Free-threaded: Not yet production-ready — test thoroughly if targeting 3.13t
samply: Interactive Rust Profiling
Interactive profiler that opens results in the Firefox Profiler UI. Supports macOS (dtrace), Linux (perf), and Windows (ETW).
Installation
cargo install samplyWhy samply
| Feature | samply | cargo-instruments | perf + flamegraph |
|---|---|---|---|
| UI | Firefox Profiler (web) | Instruments.app | Static SVG |
| Interactive | Yes (zoom, filter, search) | Yes | No |
| macOS | Yes (dtrace) | Yes (native) | No |
| Linux | Yes (perf) | No | Yes |
| Windows | Yes (ETW) | No | No |
| Call trees | Yes | Yes | Flamegraph only |
| Timeline | Yes | Yes | No |
Basic Workflow
Step 1: Build with Debug Info
# Release build with debug info (fast + symbols)
cargo build --release
# Or set in Cargo.toml:
[profile.release]
debug = true # Full debug info
# debug = "line-tables-only" # Smaller, still usefulStep 2: Profile
# Profile a binary
samply record ./target/release/my-binary
# Profile with arguments
samply record ./target/release/my-binary --arg1 value1 < input.txt
# Profile for specific duration
samply record --duration 10 ./target/release/my-binary
# Profile an already-running process (by PID)
samply record --pid 12345Step 3: Analyze
samply automatically opens the Firefox Profiler UI in your browser. The UI provides:
- Call tree: Hierarchical function call breakdown
- Flame graph: Visual representation of call stacks
- Timeline: CPU activity over time
- Source view: Line-level timing (with debug info)
- Marker chart: Custom markers and events
macOS Setup
SIP (System Integrity Protection) Considerations
On macOS, samply uses dtrace which may need elevated permissions:
# Option 1: Run with sudo (simplest)
sudo samply record ./target/release/my-binary
# Option 2: Sign the binary for dtrace (no sudo needed)
codesign -s - -f --entitlements entitlements.plist ./target/release/my-binaryEntitlements plist for dtrace:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>Apple Silicon Notes
- samply works on Apple Silicon (M1/M2/M3/M4)
- ARM PMU counters may be limited without SIP modifications
- CPU frequency scaling is less of an issue than on Intel
Reading the Firefox Profiler UI
Call Tree Tab
- Self time: Time spent in the function itself (not callees)
- Total time: Self time + time in all callees
- Sort by self time to find hotspots
Flame Graph Tab
- Width = time spent
- Bottom = entry points (main, thread start)
- Top = leaf functions (actual work)
- Look for wide bars — those are your hotspots
- Click to zoom into a subtree
Timeline Tab
- Shows CPU activity over the profiling duration
- Select a time range to focus the call tree on that window
- Useful for finding startup vs steady-state performance
Filtering
- Search box: Filter by function name
- Call tree filter: Show only paths matching a pattern
- Invert call tree: Bottom-up view (start from hotspots)
Advanced Usage
Profile Cargo Tests
# Build test binary, then profile it
cargo test --no-run --release
samply record ./target/release/deps/my_test-<hash>Profile Benchmarks
# Build benchmark binary
cargo bench --no-run
samply record ./target/release/deps/my_bench-<hash> --benchCompare Profiles
1. Save profile: Firefox Profiler → Share → Save to file 2. Load two profiles in separate tabs 3. Compare call trees side by side
Markers (Custom Events)
samply supports recording custom markers for event-based profiling:
# With environment variable markers
SAMPLY_MARKERS=1 samply record ./target/release/my-binaryIntegration with Other Tools
With cargo-pgo
# Profile first to understand hotspots
samply record ./target/release/my-binary
# Then PGO to optimize the hot paths
cargo pgo build
./target/release/my-binary < workload.txt
cargo pgo optimizeWith cargo-wizard
# Set up profiling-friendly profile
[profile.profiling]
inherits = "release"
debug = true
strip = false
cargo build --profile profiling
samply record ./target/profiling/my-binaryTips
- Always use release builds: Debug builds are too slow for meaningful profiling
- Keep debug info:
debug = trueordebug = "line-tables-only"in release profile - Don't strip symbols:
strip = falsewhen profiling - Representative workload: Profile real usage patterns, not synthetic benchmarks
- Warm up: Run the workload once before profiling to avoid measuring startup/cache effects
- Multiple runs: Profile several times to check consistency
- Firefox Profiler: Works in any browser, not just Firefox
- Sharing: The Firefox Profiler can generate shareable links