Code Coverage in Rust: From Zero to Confidence

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Code Coverage is More Than Just a CI/CD Checkbox?

\n\n

Imagine: you wrote a Rust module for parsing complex configuration files. The compiler is happy, tests pass, but a month later production crashes with an error on a rare edge case. Sound familiar? This is where code coverage in Rust comes into play — a metric that shows what percentage of your code is actually executed during tests.

\n\n

But let's be honest: chasing 100% coverage is a trap. It's much more important to understand which lines remain untested and why. In this article, we'll break down three main tools for Rust: tarpaulin, grcov, and the built-in LLVM toolchain. You'll learn how to turn dry percentages into actionable insights for refactoring.

\n\n

Why Should a Rust Developer Care About Coverage?

\n

Rust is famous for memory safety, but that doesn't eliminate logic errors. Code coverage helps:

\n
    \n
  • Detect "dead code" — functions or branches that are never called.
  • \n
  • Increase confidence during refactoring — knowing critical paths are covered by tests.
  • \n
  • Improve documentation — tests often serve as the best documentation if they cover all scenarios.
  • \n
\n

The main metric is line coverage, but we'll also look at branch coverage, which is especially important in Rust due to the abundance of match expressions.

\n\n

Tool #1: cargo-tarpaulin — Simplicity and Speed

\n

tarpaulin is the most popular tool for code coverage in Rust. It works "out of the box" and requires no complex setup. Installation:

\n
cargo install cargo-tarpaulin
\n

Running a basic analysis:

\n
cargo tarpaulin --out Html
\n

This command generates an HTML report in the tarpaulin-report.html folder. Open it — and you'll see a color map of your project: green lines (covered by tests), red lines (uncovered), and gray lines (non-executable, e.g., comments).

\n\n

Example: Testing a Simple Function

\n

Create a file src/lib.rs with a factorial function:

\n
pub fn factorial(n: u32) -> u32 {\n    match n {\n        0 | 1 => 1,\n        _ => n * factorial(n - 1),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_factorial() {\n        assert_eq!(factorial(0), 1);\n        assert_eq!(factorial(1), 1);\n        assert_eq!(factorial(5), 120);\n    }\n}
\n

Run tarpaulin:

\n
cargo tarpaulin --ignore-tests --out Html
\n

The --ignore-tests flag excludes the test code itself from the report. Result: 100% coverage for this function. But let's make it more complex — add error handling:

\n
pub fn factorial_safe(n: i32) -> Result<u32, String> {\n    if n < 0 {\n        return Err("Negative number!".to_string());\n    }\n    Ok(factorial(n as u32))\n}
\n

Now tarpaulin will show that the Err branch is not covered by tests. Add a test:

\n
#[test]\nfn test_negative() {\n    assert!(factorial_safe(-5).is_err());\n}
\n

After running again, coverage returns to 100%.

\n\n

Advanced tarpaulin Settings

\n

For large projects, use:

\n
    \n
  • --fail-under 80 — exit with an error if coverage is below 80%.
  • \n
  • --skip-clean — don't rebuild the project if changes are minor.
  • \n
  • --engine llvm — use the LLVM toolchain (experimental but more accurate).
  • \n
\n\n

Tool #2: grcov — The King of Integrations

\n

grcov is a more flexible tool that works with the LLVM toolchain. It's especially useful if you use CI/CD (GitHub Actions, GitLab

Blogs

Book Recommendations