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 |
|---|---|---|
|
deny |
code that is outright wrong or useless ( |
|
warn |
code that is most likely wrong |
|
warn |
non-idiomatic code ( |
|
warn |
code that does something simple in a complicated way |
|
warn |
code that could be faster ( |
|
allow |
stricter lints with occasional false positives ( |
|
allow |
lints that restrict the language, to be enabled individually ( |
|
allow |
new lints still being refined |
|
allow |
checks on |
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.
Source: Clippy: Clippy’s Lints
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
Source: rustfmt configuration options
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 checkor Clippy (rust-analyzer.check.command = "clippy"); -
assists (code actions): fill match arms, add missing trait items, extract function or variable, convert
if lettomatch, 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
mainfrom a code lens; structural search and replace.
Source: rust-analyzer manual
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 |
|---|---|
|
checks |
|
policy checks: allowed licenses, banned or duplicate crates, allowed registries and git sources, plus
advisories — configured in |
|
lists dependencies with newer versions available ( |
|
find dependencies declared in |
|
detects breaking API changes before you publish |
|
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
Source: RustSec Advisory Database
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
Source: Clippy: GitHub Actions
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.
References
-
The Clippy book (usage, configuration, lint configuration, CI)
-
The Rust Reference: Diagnostic attributes (allow, expect, warn, deny, forbid)
-
Rust Design Patterns: #![deny(warnings)] (community)