The Cargo Guide

Lints, Fixups, and Maintenance Commands

Why These Commands Matter

Cargo is not only a builder and package manager. It also sits at the center of several maintenance workflows: automatic fixups, formatting, linting, and edition migration. A useful mental model is:

  • cargo fix applies compiler-driven code suggestions
  • cargo fmt runs Rust formatting conventions through rustfmt
  • cargo clippy runs extra lints through Clippy

Together, these commands help keep a codebase buildable, readable, idiomatic, and easier to migrate over time.

Cargo as Orchestrator vs Tool Owner

One important distinction is that Cargo does not own all of these tools equally.

cargo fix is a true Cargo subcommand that works by driving cargo check and applying compiler suggestions.

By contrast, cargo fmt and cargo clippy are external Cargo commands distributed with the Rust toolchain as optional components. They are invoked through Cargo's command model, but the actual formatting and lint logic belongs to rustfmt and Clippy rather than to Cargo itself.

A useful mental model is:

  • Cargo directly orchestrates fixup workflow for cargo fix
  • Cargo is a convenient front door for rustfmt and Clippy

A Small Example Package

Suppose we start with a small package:

cargo new maintenance_demo --lib
cd maintenance_demo

Manifest:

[package]
name = "maintenance_demo"
version = "0.1.0"
edition = "2024"

Library source:

pub fn add(a:i32,b:i32)->i32{a+b}

This tiny crate is enough to demonstrate formatting, linting, and automated fix suggestions.

cargo fix at a High Level

cargo fix applies compiler suggestions emitted by rustc diagnostics. It is designed to automate fixes the compiler already knows how to describe.

Example:

cargo fix

A useful mental model is:

  • cargo fix is not a general refactoring engine
  • it applies actionable suggestions coming from the compiler's diagnostics pipeline

How cargo fix Works

cargo fix runs through the cargo check pipeline under the hood and applies fixable suggestions to source files.

That means it is fundamentally tied to code that Cargo can analyze through normal checking. If some code is hidden behind inactive features or inactive target-specific cfg conditions, cargo fix will not see or change that code unless you activate those configurations explicitly.

A Simple cargo fix Example

Suppose src/lib.rs contains code that triggers a compiler warning with a machine-applicable suggestion.

Illustrative source:

pub fn greet() {
    let mut name = "alice";
    println!("{name}");
}

Then a normal fix workflow is:

cargo fix

After the run, Cargo may have applied one or more compiler-suggested changes automatically and then shown any remaining warnings.

Feature-Conditioned Fixups

cargo fix only fixes code that is actually compiled during the check. That means feature-controlled code often requires explicit feature selection.

Example manifest:

[features]
default = []
json = []

Feature-gated code:

#[cfg(feature = "json")]
pub fn output_mode() -> &'static str {
    "json"
}

To ensure that code is analyzed and eligible for fixes:

cargo fix --features json

For larger crates, it is often useful to run fixups across multiple feature configurations rather than assuming the default feature set is enough.

Target-Conditioned Fixups

Platform-specific code also needs explicit target selection if you want cargo fix to analyze it.

Example:

#[cfg(windows)]
pub fn platform_name() -> &'static str {
    "windows"
}

To fix code for a target-specific branch:

cargo fix --target x86_64-pc-windows-gnu

A useful mental model is:

  • cargo fix only sees code in the active compilation universe
  • features and targets define that universe

Target Selection for cargo fix

By default, cargo fix behaves like cargo check --all-targets, which means it considers all targets unless you narrow the selection.

Examples:

cargo fix --lib
cargo fix --bin app
cargo fix --test api
cargo fix --example quickstart
cargo fix --bench perf

This is especially useful in packages with many binaries, tests, benches, or examples, where you want to limit fixups to a specific part of the crate.

Workspace-Oriented Fixups

In workspaces, cargo fix follows normal Cargo package-selection rules.

Examples:

cargo fix --workspace
cargo fix -p core
cargo fix --workspace --exclude tools

A useful workflow for larger repositories is to apply fixes in smaller scopes rather than trying to change the whole workspace at once.

Broken-Code Mode

Sometimes the compiler's suggested fix is close but not fully correct in context. In those cases, cargo fix normally backs out failed edits.

If you want Cargo to leave the partially fixed code in place so you can inspect and finish it manually, use:

cargo fix --broken-code

This is especially useful during large migrations where some automated changes are right in spirit but still need human cleanup.

cargo fmt at a High Level

cargo fmt formats the crate's source using rustfmt.

Example:

cargo fmt

A useful mental model is:

  • formatting is about code shape, not semantics
  • cargo fmt is the Cargo entry point for rustfmt

Because cargo fmt is an external Cargo command provided as an optional component, it may require installation through the toolchain if it is not already available.

A Simple Formatting Example

Suppose src/lib.rs contains poorly formatted code:

pub fn add(a:i32,b:i32)->i32{a+b}

Then:

cargo fmt

can rewrite it into standard Rust style:

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

A useful principle is that formatting should generally be automatic and boring.

Formatting as a Maintenance Primitive

Formatting is often the lowest-friction maintenance command because it tends to reduce meaningless diffs and style arguments.

A healthy workflow often treats cargo fmt as routine rather than exceptional.

Example:

cargo fmt
cargo test

This keeps code shape consistent before deeper review or linting.

cargo clippy at a High Level

cargo clippy runs Clippy lints for a package.

Example:

cargo clippy

A useful mental model is:

  • compiler warnings catch some issues
  • Clippy adds a broader layer of style, correctness, and idiom-focused lints

Like cargo fmt, cargo clippy is an external Cargo command distributed as an optional component rather than something built directly into Cargo.

A Simple Clippy Example

Suppose src/lib.rs contains code that works but is not especially idiomatic.

Illustrative example:

pub fn is_large(n: i32) -> bool {
    if n > 10 {
        return true;
    }
    false
}

Then:

cargo clippy

may suggest a simpler and more idiomatic structure.

A useful mental model is:

  • Clippy is not only about correctness
  • it is also about improving maintainability and idiomatic expression

Clippy in Everyday Workflow

A common maintenance rhythm is:

cargo fmt
cargo clippy
cargo test

This sequence means:

  • normalize formatting first
  • surface lint advice second
  • verify behavior third

It is not the only order, but it is a practical one because formatting noise does not distract from lint and test review.

When cargo fix and Clippy Overlap

cargo fix and Clippy both interact with lint-driven improvement, but they are not the same thing.

A useful distinction is:

  • cargo fix applies machine-applicable compiler suggestions
  • cargo clippy reports additional lints, many of which still require judgment or manual edits

In practice, a maintenance session may use both:

cargo fix
cargo clippy

Edition Migration Workflows

cargo fix also supports edition migration. The high-level migration flow is:

  1. run cargo fix --edition
  2. update the package's edition field in Cargo.toml
  3. run tests and follow-up checks

A minimal package manifest before migration might look like this:

[package]
name = "edition_demo"
version = "0.1.0"
edition = "2021"

Then the migration command is:

cargo fix --edition

After that, the edition field is updated manually:

[package]
name = "edition_demo"
version = "0.1.0"
edition = "2024"

Why Edition Migration Is Not Fully Automatic

cargo fix --edition can automate many compiler-known changes, but it does not rewrite everything in every case.

A useful mental model is:

  • Cargo can apply compiler-known migrations
  • some edition changes still need human judgment
  • inactive features, inactive targets, and macro-heavy code may require extra work

Edition Idioms

Cargo also supports edition-idiom updates for the current edition.

Example:

cargo fix --edition-idioms

This is useful when a codebase already targets a given edition but still wants to adopt the preferred style and idioms associated with that edition more consistently.

Advanced Migration in Large Workspaces

Large projects and workspaces do not always migrate all at once. A useful incremental model is:

  • migrate one package at a time in a workspace
  • or migrate individual targets one at a time within a package

This is especially practical when multiple binaries, tests, or examples make a full migration noisy or risky.

Migrating One Target at a Time

Suppose a package has multiple binaries and examples.

Layout:

edition_demo/
ā”œā”€ā”€ Cargo.toml
ā”œā”€ā”€ src/
│   ā”œā”€ā”€ lib.rs
│   ā”œā”€ā”€ main.rs
│   └── bin/
│       └── admin.rs
└── examples/
    └── quickstart.rs

Then a narrower migration command may be useful:

cargo fix --edition --bin admin
cargo fix --edition --example quickstart

This helps isolate migration work to smaller surfaces.

Allowing Dirty or Staged Working Trees

cargo fix is conservative about modifying source trees. If your working directory already has changes, Cargo may refuse to proceed unless you opt in.

Examples:

cargo fix --allow-dirty
cargo fix --allow-staged

This is useful in practiced workflows, but it is healthiest when you understand exactly what changes are already present.

A Practical Maintenance Workflow

For many crates, a healthy maintenance pass looks something like this:

cargo fmt
cargo fix
cargo clippy
cargo test

And for a featureful or multi-target package:

cargo fix --all-features
cargo fix --target x86_64-pc-windows-gnu
cargo clippy --all-features
cargo test --all-features

This keeps formatting, compiler-driven fixes, lint review, and behavior verification in one coherent loop.

A Workspace Maintenance Workflow

In a workspace, it is often useful to combine package selection with maintenance commands.

Examples:

cargo fix --workspace
cargo clippy --workspace
cargo test --workspace

Or narrower:

cargo fix -p core
cargo clippy -p core
cargo test -p core

This is especially valuable in larger monorepos where one package's maintenance changes should not automatically touch every member.

Automated Suggestions vs Human Judgment

A healthy mental model is that automated suggestions are assistance, not automatic truth.

cargo fix can apply machine-applicable compiler suggestions, and Clippy can surface many useful improvements, but a maintainer still needs to judge whether a suggested change improves clarity, preserves intended semantics, and fits the crate's public API commitments.

A Small End-to-End Example

Suppose src/lib.rs starts like this:

pub fn add(a:i32,b:i32)->i32{a+b}
 
pub fn is_large(n: i32) -> bool {
    if n > 10 {
        return true;
    }
    false
}

A small maintenance session could look like:

cargo fmt
cargo fix
cargo clippy
cargo test

After formatting and lint-guided cleanup, the code may become easier to read and maintain even if the core behavior did not change.

When rustfmt and Clippy Are Logically Separate

Although cargo fmt and cargo clippy are invoked through Cargo, they remain logically separate tools.

A useful distinction is:

  • Cargo gives them package and workspace context
  • rustfmt owns formatting rules
  • Clippy owns its extra lint set

This matters because understanding a result sometimes means asking whether the behavior came from Cargo orchestration or from the external tool's own rules and configuration.

Common Beginner Mistakes

Mistake 1: treating cargo fix like a general-purpose refactoring engine instead of a compiler-suggestion applicator.

Mistake 2: forgetting that cargo fix only sees code in the active feature and target configuration.

Mistake 3: assuming cargo fmt and cargo clippy are built into Cargo rather than optional external Cargo commands.

Mistake 4: trying to migrate editions without re-testing after updating Cargo.toml.

Mistake 5: applying every lint suggestion without judgment.

Mistake 6: running maintenance commands across a large workspace without scoping them deliberately.

Hands-On Exercise

Create a small library crate and run a complete maintenance pass.

Start here:

cargo new maintenance_lab --lib
cd maintenance_lab

Use this source:

pub fn add(a:i32,b:i32)->i32{a+b}
 
pub fn is_large(n: i32) -> bool {
    if n > 10 {
        return true;
    }
    false
}

Then run:

cargo fmt
cargo fix
cargo clippy
cargo test

If rustfmt or Clippy are not installed in your current toolchain, install their toolchain components first and rerun. Then compare the code before and after to see which changes came from formatting, which came from compiler-driven fixes, and which came from lint review.

Mental Model Summary

A strong mental model for lints, fixups, and maintenance commands in Cargo is:

  • cargo fix is Cargo's compiler-suggestion application workflow, built around cargo check
  • cargo fmt and cargo clippy are external Cargo commands that Cargo orchestrates but does not logically own
  • formatting, linting, and fixups are related but distinct maintenance surfaces
  • edition migration is a structured workflow centered on cargo fix --edition, manual manifest update, and follow-up testing
  • larger crates and workspaces benefit from deliberate feature, target, and package scoping during maintenance

Once this model is stable, Cargo maintenance commands become much easier to use as a disciplined codebase hygiene workflow instead of as isolated convenience commands.