How cargo fix cleans up your codebase
You are building a CLI tool. It compiles. It runs. But the terminal is flooded with yellow text. warning: variable does not need to be mutable. warning: unused import. You have been ignoring them for months because fixing them one by one feels like chores. Then Rust releases a new edition, and suddenly your code does not compile at all because of deprecated syntax. You need a way to clean this up without touching every file manually.
cargo fix is the tool that automates the compiler's suggestions. The Rust compiler does not just tell you what is wrong. It often tells you exactly how to fix it. cargo fix listens to those suggestions and applies them across your codebase. It handles the boring stuff: removing unused variables, updating syntax for new editions, fixing trait bounds. It does not fix logic. It does not know if your algorithm is wrong. It only touches code where the compiler is 100% sure the fix is safe and non-breaking.
Think of cargo fix as a spellchecker that actually rewrites the sentence. You write a sentence with a redundant word, and the spellchecker deletes it. cargo fix does this for Rust syntax and warnings. It preserves the behavior of your code while removing the noise.
What cargo fix actually does
cargo fix runs cargo check with special flags that ask the compiler to emit machine-readable suggestions. The compiler analyzes your code, collects all suggestions, and returns them. cargo fix parses the suggestions and applies them to the source files. It then re-runs the check to ensure the fixes did not introduce new errors.
The tool is conservative. It only applies suggestions that are guaranteed to be non-breaking. A non-breaking fix means the change does not alter the runtime behavior of the program. Removing an unused variable is non-breaking. Updating extern crate to the modern syntax is non-breaking. Changing Result::unwrap to expect is not non-breaking, so cargo fix will never do that. You must make semantic changes yourself.
/// Demonstrates a warning that cargo fix resolves automatically.
/// The compiler detects that unused_value is assigned but never read.
/// cargo fix will remove this line entirely.
fn main() {
let x = 5;
let unused_value = x + 10;
println!("x is {}", x);
}
When you run cargo fix on this code, the tool removes the unused_value line. The program behaves exactly the same way, but the warning is gone. The compiler is confident that removing the line is safe because the value is never used.
Running cargo fix in your workflow
By default, cargo fix refuses to run if you have uncommitted changes in your version control system. This is a safety feature. The tool modifies files in place. If you have work in progress, cargo fix could overwrite your changes or create a messy diff. You must pass the --allow-dirty flag to tell cargo fix that you accept the risk.
# Preview changes without modifying files.
# This is safe to run in CI or before committing.
cargo fix --dry-run
# Apply fixes to all warnings in the current workspace.
# Requires --allow-dirty if you have uncommitted changes.
cargo fix --allow-dirty
The --dry-run flag is essential for verification. It shows you exactly what changes cargo fix plans to make. Use it to inspect the diff before applying anything. If the diff looks wrong, do not run the command without --dry-run. The compiler is usually right, but suggestions can sometimes be context-dependent. A suggestion that looks correct in isolation might break something else in your codebase.
Convention aside: the community standard is to run cargo fix --allow-dirty locally, review the diff, and commit. In CI pipelines, use cargo fix --dry-run to ensure the code is clean without modifying files. Never run cargo fix without --dry-run in a shared repository or automated build.
Migrating to a new Rust edition
Rust editions introduce changes to the language and standard library. Some changes are breaking. Others are deprecations that the compiler can fix automatically. cargo fix can migrate your code to a newer edition by updating syntax and standard library usage.
This is particularly useful for large codebases where manual updates are tedious. For example, Rust 2018 removed the need for extern crate declarations. If you are migrating from Rust 2015 to Rust 2018, cargo fix can remove all extern crate lines automatically.
// src/lib.rs
// In older Rust editions, you needed explicit extern crate declarations.
// Rust 2018 and later removed this requirement.
// cargo fix --edition can remove these automatically.
extern crate serde;
use serde::Serialize;
#[derive(Serialize)]
pub struct Config {
pub name: String,
}
When you run cargo fix --edition --allow-dirty, the tool removes the extern crate serde; line. The use statement remains. The code compiles with the new edition. The behavior is identical.
Edition migration can also update standard library usage. For example, some methods were renamed or moved to different modules. cargo fix updates the calls to match the new API. Always review the generated diff before committing. Edition migration can touch many files, and you want to ensure the changes are correct.
Pitfalls and limitations
cargo fix is a powerful tool, but it has limits. It only fixes code that compiles successfully. If you have hard errors, cargo fix stops. You must fix errors manually before asking cargo fix to clean up warnings. The compiler cannot suggest fixes for code that does not parse or type-check.
If you run cargo fix on broken code, you get an error like: error: cargo fix cannot be run on broken code. Fix the errors first. Then run cargo fix to clean up the warnings.
The tool does not run tests automatically. After applying fixes, always run cargo test to ensure the automated changes did not introduce regressions. cargo fix is conservative, but edge cases exist. A fix that is safe in theory might interact badly with your specific code. Tests are the final safety net.
cargo fix cannot resolve logic errors or ambiguous code that requires human judgment. If the compiler is unsure about the fix, it will not suggest one. For example, if you have a variable that is used in a complex way, the compiler might not be able to determine if removing it is safe. In that case, you must fix the warning manually.
Convention aside: cargo fmt and cargo fix are friends. cargo fix changes logic and structure. cargo fmt changes whitespace. Run cargo fix first, then cargo fmt. If you run them in the wrong order, cargo fmt might mess up the formatting of the changes cargo fix just made. The community workflow is cargo fix --allow-dirty followed by cargo fmt.
When to use cargo fix
Use cargo fix --allow-dirty when you have a compiling project and want to batch-remove warnings like unused variables, unused imports, and deprecated syntax. Use cargo fix --edition --allow-dirty when you are migrating to a newer Rust edition and need to update syntax and standard library usage automatically. Use cargo fix --dry-run when you want to preview changes without modifying files, or when running in a CI pipeline. Reach for manual editing when the compiler suggests a fix that changes semantics, or when you need to refactor logic that cargo fix cannot understand.
Do not rely on cargo fix to fix everything. It is a janitor, not an architect. It cleans up the mess, but it does not design the house. You still need to write good code. You still need to review the diff. You still need to run the tests.
Trust the compiler's suggestion, but verify the diff. The compiler is smart, but it does not know your intent. If a suggestion looks suspicious, investigate it. Better to spend five minutes checking a diff than debugging a subtle regression later.