Tooling: Clippy, rustfmt & rust-analyzer

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Rust 1.99.0, Clippy 0.1.99 and the 2024 edition, checked on 2026-10-06. Three tools ship with the default toolchain and are used by almost every Rust project: Clippy (a linter with hundreds of lints beyond the compiler’s), rustfmt (the formatter that enforces the official style) and rust-analyzer (the language server behind IDE features). This page covers how to use and configure them, the ecosystem’s supply-chain tools, and a CI setup that ties them together.

Clippy

Install with rustup component add clippy (part of the default profile) and run cargo clippy. It compiles the code and reports lints, each with an explanation and usually a suggested fix:

cargo clippy                                   # lint the package
cargo clippy --all-targets --all-features      # include tests, examples, benches; every feature
cargo clippy --fix --allow-dirty               # apply machine-applicable suggestions
cargo clippy -- -W clippy::pedantic            # enable a group for this run

Source: Clippy: Usage

Lint groups

Group Default level Contents

clippy::correctness

deny

code that is outright wrong or useless (eq_op, absurd_extreme_comparisons)

clippy::suspicious

warn

code that is most likely wrong

clippy::style

warn

non-idiomatic code (len_zero, needless_return)

clippy::complexity

warn

code that does something simple in a complicated way

clippy::perf

warn

code that could be faster (useless_vec, redundant_clone)

clippy::pedantic

allow

stricter lints with occasional false positives (must_use_candidate, missing_errors_doc)

clippy::restriction

allow

lints that restrict the language, to be enabled individually (unwrap_used, print_stdout, indexing_slicing)

clippy::nursery

allow

new lints still being refined

clippy::cargo

allow

checks on Cargo.toml metadata

Every example on this site’s Rust pages passes cargo clippy — -D warnings, which is why some of them use arrays rather than vec![], is_multiple_of rather than % n == 0, or then_some rather than then(|| ..) — those are Clippy suggestions. Never enable restriction as a whole group: some of its lints contradict each other.

Configuring lints: allow, expect and reasons

Lint levels are set with attributes on any item, or for the whole crate in Cargo.toml (below). #[allow] silences a lint; #[expect] (stable since 1.81) silences it and warns if the lint stops firing, so stale suppressions get cleaned up. Both accept a reason:

#[expect(clippy::cast_possible_truncation, reason = "ratings are always 1-5")]
fn rating_byte(r: u32) -> u8 {
    r as u8
}

#[allow(dead_code, reason = "kept for the v1 import format")]
fn legacy_parse(_line: &str) {}

fn main() {
    println!("{}", rating_byte(4));
}

clippy.toml (or .clippy.toml) at the package root configures individual lints, such as the MSRV Clippy should respect, thresholds and disallowed APIs:

# clippy.toml
msrv = "1.85"                              # do not suggest APIs newer than this
too-many-arguments-threshold = 6
cognitive-complexity-threshold = 20
disallowed-methods = [
    { path = "std::env::set_var", reason = "configuration must come from Settings" },
]
allow-unwrap-in-tests = true               # for clippy::unwrap_used

The [lints] table and denying warnings in CI

Put the project’s lint policy in Cargo.toml (inherited by every member of a workspace; see Cargo & Workspaces) rather than in attributes:

[lints.rust]
unsafe_code = "forbid"
missing_docs = "warn"

[lints.clippy]
pedantic = { level = "warn", priority = -1 }
module_name_repetitions = "allow"
unwrap_used = "warn"

To make CI fail on warnings, pass -D warnings on the command line in CI (cargo clippy — -D warnings, or RUSTFLAGS="-D warnings"). Do not put #![deny(warnings)] in the source: a new compiler or Clippy release can add a lint and break your crate — and every crate that depends on it — for no fault of yours. The community Rust Design Patterns lists #![deny(warnings)] as an anti-pattern for that reason.

rustfmt

cargo fmt formats the whole package according to the official Rust Style Guide; cargo fmt --check exits with an error if anything would change, which is the usual CI gate. Formatting is not a matter of taste in Rust: almost all projects use the defaults, so code looks the same everywhere.

# rustfmt.toml -- keep it small; stable options only
style_edition = "2024"         # the formatting style; defaults to the crate's edition
max_width = 100                # the default
use_field_init_shorthand = true

Since the 2024 edition, rustfmt’s style edition is independent of the language edition: a crate can move to Rust 2024 and keep the 2021 formatting style, or the other way round. Style edition 2024 changes, among other things, how identifiers are sorted in imports ("version sorting": u8 before u16). Use #[rustfmt::skip] on an item, such as a hand-aligned lookup table, to leave it alone.

rust-analyzer

rust-analyzer is the official language server, used by VS Code, Neovim, Helix, Zed, Emacs and others. Beyond completion and go-to-definition, the features that change how you work:

  • inlay hints for inferred types, parameter names, lifetimes, chained iterator types and closure captures;

  • inline diagnostics from cargo check or Clippy (rust-analyzer.check.command = "clippy");

  • assists (code actions): fill match arms, add missing trait items, extract function or variable, convert if let to match, generate getters, add #[derive], import the item under the cursor;

  • Expand macro recursively — see what a macro_rules! or derive produces;

  • View memory layout of a type, View crate graph, View HIR/MIR for curious readers;

  • run or debug a test or main from a code lens; structural search and replace.

cargo fix

cargo fix applies the compiler’s machine-applicable suggestions: removing unused imports, adding missing mut, and above all edition migrations (cargo fix --edition, see Toolchain, rustup & Editions). Combined with cargo clippy --fix, it can clean up large code bases mechanically; review the diff before committing.

Supply-chain and dependency tools

Tool What it does

cargo audit

checks Cargo.lock against the RustSec advisory database for crates with known vulnerabilities, yanked versions and unmaintained crates

cargo deny

policy checks: allowed licenses, banned or duplicate crates, allowed registries and git sources, plus advisories — configured in deny.toml

cargo outdated

lists dependencies with newer versions available (cargo update --dry-run --verbose shows the compatible updates without a plugin)

cargo machete / cargo udeps

find dependencies declared in Cargo.toml but never used

cargo semver-checks

detects breaking API changes before you publish

cargo vet

records human audits of third-party crates

cargo install cargo-audit cargo-deny cargo-machete
cargo audit
cargo deny init && cargo deny check
cargo machete

A GitHub Actions workflow

The Clippy book suggests running Clippy in CI with warnings denied on the command line. A complete workflow for a workspace:

name: ci
on:
  push:
    branches: [main]
  pull_request:

env:
  CARGO_TERM_COLOR: always

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt
      - uses: Swatinem/rust-cache@v2
      - run: cargo fmt --all --check
      - run: cargo clippy --workspace --all-targets --all-features -- -D warnings
      - run: cargo test --workspace --all-features
      - run: cargo doc --workspace --no-deps
        env:
          RUSTDOCFLAGS: -D warnings

  msrv:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: dtolnay/rust-toolchain@1.85
      - run: cargo check --workspace

  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: EmbarkStudios/cargo-deny-action@v2

Where the books are outdated

Azam (Advanced Rust, chapter 2) introduces rustfmt and debugging-oriented tooling; since then #[expect] (1.81), the [lints] table (1.74) and rustfmt style editions (2024) have changed how lint and format policies are usually expressed.