# cargo-crap scores Rust functions by complexity against uncovered lines and gates CI on it

> cargo-crap is a cargo subcommand that pairs cyclomatic complexity with LCOV line coverage to rank the functions where a change can land unnoticed. It reads coverage you already produce, never runs your tests itself, and its two CI gates behave very differently on an existing codebase.

**minikin/cargo-crap** — Change Risk Anti-Patterns (CRAP) metric for Rust projects

- Repository: https://github.com/minikin/cargo-crap
- Website: https://crates.io/crates/cargo-crap
- Stars: 413 · Forks: 13
- Language: Rust
- License: MIT
- Published: 2026-09-15 · Updated: 2026-09-15 · Language: en
- Canonical page: https://hysenlabs.com/projects/minikin-cargo-crap

## The score is complexity squared times uncovered lines cubed

The whole metric is one line:

```text
CRAP(m) = comp(m)² × (1 − cov(m)/100)³ + comp(m)
```

comp is the function's cyclomatic complexity and cov its line coverage as a percentage. The example in the documentation makes the shape clear: a function with CC 12 and no tests scores 156, while the same function at full coverage scores 12, which is just its complexity. Savoia and Evans introduced the metric in 2007. The asymmetry at the end of the formula is the part that changes how you read a report. CRAP is never below CC, so tests alone cannot rescue a function whose complexity sits above your threshold; it stays marked as failing however well it is covered. Two things pull a score down: writing tests over the uncovered lines, which moves coverage up and pulls the score toward CC, and splitting the function, which lowers CC itself. At 100% coverage the two values are equal, which makes the score a plain restatement of complexity.

## cargo-crap never runs your tests, it reads the LCOV file you already have

That constraint shapes the install. A pre-built binary is the short path, and a source build needs Rust 1.88 or newer:

```bash
cargo binstall cargo-crap          # pre-built binary
cargo install cargo-crap --locked  # from source, Rust 1.88 or newer
```

On Arch Linux the same tool is available as paru -S cargo-crap, and the install guide covers the release archive downloads for Linux and macOS along with the Windows steps. The workflow then has three commands rather than one, because coverage has to be produced first:

```bash
cargo install cargo-llvm-cov
cargo llvm-cov --lcov --output-path lcov.info
cargo crap --lcov lcov.info
```

cargo-llvm-cov is the usual producer of that file, and cargo tarpaulin is named as an alternative that works too. In a workspace you add the workspace flag to both the coverage command and the cargo crap command, since a partial run would score functions against missing coverage. The getting-started guide also documents a summary mode and a package filter for narrowing the report. The output is a table sorted worst first, with CRAP score, CC, coverage, function name and file location, and a marker column that separates failures from the rest.

## Above the default threshold of 30 the gate fails every pull request

The verdict travels in the exit code. Zero means no gate tripped, one means the absolute gate or the regression gate tripped, and two means the run itself failed, which is the code to alert on separately from a policy failure. The documented workflow runs on every push to main and every pull request and fails when a function scores above the default threshold of 30. Its job body installs both tools from release binaries through an install action, so neither is compiled from source, and then runs coverage followed by the check:

```yaml
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: llvm-tools-preview
      - uses: taiki-e/install-action@v2
        with:
          tool: cargo-llvm-cov,cargo-crap
      - run: cargo llvm-cov --lcov --output-path lcov.info
      - run: cargo crap --lcov lcov.info --fail-above
```

The obvious trap is spelled out in the documentation: on a codebase that already has functions above 30, this gate fails every pull request until they are fixed, which makes it useless as an immediate adoption path. The regression gate is the alternative. It compares against a saved baseline and fails only when a score goes up. A function added since the baseline is reported as New and does not trip it, so the intended sequence is to keep the regression gate first and add the absolute gate once the old offenders are gone.

## uncovered-hints turns the table into a list of line ranges

A score tells you where to look, not what to write. Setting uncovered-hints to true in .cargo-crap.toml adds an Uncovered column to the human, markdown and pull-request comment tables, giving each function's uncovered line ranges so the test cases can be aimed at specific blocks. What it does not do is decide whether those lines are worth covering, which is why the scoring rules exclude test code entirely. Functions marked with the test attribute and modules behind a cfg(test) block are skipped, and the tests, benches and examples directories are excluded by default, so the numbers describe production code rather than your own test scaffolding. Complexity counting has its own edge cases, such as what exactly counts as a branch, and those are enumerated separately in the complexity reference rather than left implicit. The reporting side is deliberately layered: the human table for reading locally, markdown for pasting, and a comment format aimed at pull requests, so the same run can serve three audiences without re-running the tool.

## --duplicates pairs same-structure functions and leaves the verdict to TypeSafe

Since version 0.6.0 a second mode exists, and it is the one place the tool reaches outside your machine. The duplicates flag lists pairs of functions with the same structure and needs no coverage input at all, on the reasoning that structure alone cannot distinguish the same logic written twice from two functions that merely share a Rust idiom. Turning on triage lets a TypeSafe model answer three questions about each pair: what kind of duplication it is, whether it is worth merging, and whether a fix to one side would be missed in the other. The first answer and the merge verdict print under the pair, and JSON output carries all three. Colour is used for the verdict, red, yellow or dim, and the triage demo in examples/ shows three pairs that look equally urgent by structure alone being sent to merge, to take a parameter, and to be left alone. The important boundary is that both function bodies are sent to that service. Triage stays off until .cargo-crap.toml sets triage enabled under duplicates and TYPESAFE_API_KEY is present in the environment, and while the release binaries include the client, a source build needs the triage feature.

## SARIF output and a comment bot close the loop on pull requests

Failing a build is only half of the workflow, and the guides cover the other half. The CI guide documents uploading SARIF so the findings appear in GitHub Code Scanning rather than only in a log, which moves them into the alerting and triage tooling a team already runs. The pull-request guide covers a bot that posts the delta as a comment, so a reviewer sees which functions moved instead of digging through logs. Both fit the exit-code design, where a policy failure and a broken run are distinguishable numbers. The header of the README points at two companion pieces of writing on the maintainer's site, one on untested complexity and one on triaging duplicates, plus a recorded conference talk, and a badge guide is linked alongside them. What none of this tells you is how the tool behaves on a codebase that has never tracked coverage, where every function inherits a low coverage figure and the ranking collapses into a complexity list. Nothing in the documentation describes a migration path for that case.

## Edition 2024, Rust 1.88, and a dependency list that reads as a design note

The manifest is worth reading because it explains the shape of the tool. The crate is edition 2024 with a minimum Rust version of 1.88, a binary named cargo-crap under src/main.rs and a library named cargo_crap, and it is filed on crates.io under both a cargo plugin and a testing category. The dependencies split cleanly by job. syn with the visit and printing features, plus proc-macro2 and quote, parse Rust source; lcov reads the coverage file; globset and ignore walk the tree; rayon parallelises the work across functions; comfy-table and owo-colors draw the coloured output, and indicatif shows progress on long runs. serde_json is enabled with float_roundtrip, so JSON output preserves the score values exactly rather than rounding them on the way out, and toml is there to read .cargo-crap.toml. The single optional dependency is ureq, pinned to the rustls feature with default features off, and the comment above it is explicit that it is a small blocking client with no gzip, compiled in only when the triage feature is enabled. That is the entire network surface of the project, gated behind one feature flag. The repository also carries a Justfile, rustfmt.toml, clippy.toml, a rust-toolchain.toml, a cargo-mutants configuration and a proptest-regressions directory, which is the tooling footprint of a project that tests its own parsers.

## Conclusion

cargo-crap earns a place in a Rust pipeline that already emits LCOV data, because it adds no coverage cost of its own and turns an existing file into a ranked work list. Two things to settle before wiring it in. If your codebase already has functions scoring above 30, start with the regression gate against a saved baseline, because the default fail-above gate fails every pull request until those are fixed. And leave duplicate triage off until you have decided that sending function bodies to a third-party model is acceptable, since enabling it is a one-line change in .cargo-crap.toml and the release binaries already contain the client. Version 0.6.1 was published on 2026-09-29 and the crate is MIT licensed.

## FAQ

### What does the CRAP score in cargo-crap actually measure?

It combines cyclomatic complexity with line coverage: the score is complexity squared times the uncovered fraction cubed, plus the complexity. A function with CC 12 and no tests scores 156, and at full coverage it scores 12, which is its complexity alone. Savoia and Evans introduced the metric in 2007.

### Does cargo-crap run my tests?

No. It reads the LCOV file that a coverage tool writes, which is usually produced by cargo-llvm-cov, and cargo tarpaulin is named as an alternative that works too. You generate coverage first, then point cargo crap at the file.

### How do I install cargo-crap?

cargo binstall cargo-crap gets a pre-built binary, and cargo install cargo-crap --locked builds from source and needs Rust 1.88 or newer. On Arch Linux it is available as paru -S cargo-crap, and the install guide covers the release archives for Linux, macOS and Windows.

### What happens if I enable the fail-above gate on a codebase with old high scores?

It fails every pull request until those functions are fixed. The documented alternative is the regression gate, which compares against a saved baseline and fails only when a score goes up, with functions added since the baseline reported as New so they do not trip it.

### Does the --duplicates flag in cargo-crap send my code to a third party?

Not by default. Structure matching works on its own with no coverage input, and triage only runs once .cargo-crap.toml enables it under duplicates and TYPESAFE_API_KEY is set, at which point both function bodies are sent to TypeSafe. The release binaries include the client; a source install needs the triage feature.

### Which files does cargo-crap leave out of the score?

Test code is skipped entirely, including functions marked with the test attribute and modules behind a cfg(test) block. The tests, benches and examples directories are excluded by default as well.

## Sources

- [License: MIT](https://github.com/minikin/cargo-crap/blob/main/LICENSE)
- [minikin/cargo-crap on GitHub](https://github.com/minikin/cargo-crap)
- [Project website](https://crates.io/crates/cargo-crap)
- [README](https://github.com/minikin/cargo-crap/blob/main/README.md)
- [Releases](https://github.com/minikin/cargo-crap/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/minikin-cargo-crap
