How to Use cargo fix for Automatic Edition Migration

Use `cargo fix --edition` to automatically migrate your codebase to a newer Rust edition by applying compiler suggestions and updating syntax.

The automated edition migration tool

You just updated your Rust toolchain. The compiler warns that your project is using an old edition. You see a list of syntax changes and deprecated patterns. You stare at fifty files and think you need to open every one and hunt for the old code. That instinct is wrong. Rust has a built-in migration assistant that fixes the mechanical work for you. cargo fix --edition scans your codebase, finds the patterns that changed between editions, and applies the compiler's suggested patches. You review the changes, commit them, and move on. The tool handles the boring rewriting so you can focus on the logic that actually matters.

How editions and fixes interact

Rust editions are snapshots of the language. Every three years, the Rust team releases a new edition. It adds features, removes deprecated stuff, and sometimes changes how certain syntax works. The edition system exists so your code doesn't break when you update the compiler. You opt in to the new rules by updating the edition field in Cargo.toml.

cargo fix --edition is the bridge between editions. It doesn't just swap strings. It parses your code, identifies patterns that violate the new edition's rules, and rewrites them to match the new syntax. Think of it like a smart grammar checker that knows the rules changed. It sees a deprecated import and replaces it with the modern path. It sees a macro invocation that needs a new argument and adds it. It handles the structural updates automatically.

The tool relies on the compiler's diagnostic system. When you compile code with a newer edition, rustc emits warnings and lints for outdated patterns. Some of these diagnostics include a suggestion field marked as machine-applicable. cargo fix collects these suggestions and applies them as patches. This design means cargo fix can only fix what the compiler explicitly suggests. If the compiler doesn't emit a machine-applicable suggestion, the tool can't help. You must handle those cases manually.

The minimal workflow

Create a simple project to see the tool in action. The workflow is the same for any codebase. You run the command, review the diff, and accept the changes.

/// src/main.rs
/// A function using a pattern that might trigger edition lints.
/// cargo fix can update imports and fix deprecated syntax automatically.
fn main() {
    // In older editions, some imports were required explicitly.
    // The tool can clean up redundant imports based on the new prelude.
    let x = vec![1, 2, 3];
    println!("{:?}", x);
}

Run the migration command in your project root. The tool compiles your code with the target edition enabled. It collects all warnings and lints. It generates patches for each file. It shows you a diff. You type y to accept the changes. The tool writes the patches to disk and updates the edition field in Cargo.toml.

# Migrate to the latest edition
cargo fix --edition

The command updates your Cargo.toml to the new edition. It also updates the source files. If you want to migrate to a specific edition, pass the edition number explicitly. This is useful when you are targeting a stable release that hasn't become the default yet.

# Migrate to a specific edition
cargo fix --edition 2021

Trust the tool for syntax. Trust yourself for logic. cargo fix handles the mechanical updates. You handle the meaning.

What changes under the hood

The amount of code cargo fix touches depends on the edition gap. Jumping from 2015 to 2018 triggers massive rewrites. The 2018 edition overhauled the module system and glob imports. cargo fix updates use statements, adjusts path syntax, and fixes macro invocations. Jumping from 2021 to 2024 might touch almost nothing. The 2024 edition introduces subtle refinements. The tool adapts to what it finds.

Under the hood, cargo fix uses a component called rustfix. This library parses the compiler's JSON output. It extracts suggestions marked as MachineApplicable. The compiler classifies suggestions into categories. MachineApplicable means the fix is safe and automatic. MaybeIncorrect means the fix might be wrong and needs human review. cargo fix only applies MachineApplicable suggestions by default. This prevents the tool from breaking your code with a bad guess.

Convention aside: The community standard is to run cargo update before migrating editions. Dependencies often lag behind the compiler. Updating your dependencies first ensures you have versions that support the new edition. It reduces the chance of hitting version bounds that conflict with the migration. Run cargo update to refresh Cargo.lock, then run cargo fix --edition.

Real-world scenarios

Real projects have workspaces, uncommitted changes, and CI pipelines. The tool provides flags to handle these cases.

A monorepo has multiple crates. Running cargo fix in one crate ignores the others. Use --workspace to migrate all member crates in a single pass. The tool processes each crate sequentially. It applies fixes across the entire workspace.

# Migrate the entire workspace
cargo fix --edition --workspace

By default, cargo fix refuses to run if you have uncommitted changes. It protects you from losing work. If you have changes you want to keep, use --allow-dirty. This flag tells the tool to proceed even if the workspace is dirty. It applies the fixes on top of your current changes. Be careful. If the fixes conflict with your uncommitted edits, the tool might fail or produce messy diffs.

# Allow fixes with uncommitted changes
cargo fix --edition --allow-dirty

If you have staged changes in git, cargo fix might complain. Use --allow-staged to permit fixes on staged files. This is useful when you are preparing a commit and want to include the migration in the same batch. Combine it with --allow-dirty for full flexibility.

# Allow fixes on staged and dirty files
cargo fix --edition --allow-dirty --allow-staged

Convention aside: The community calls this the "minimum unsafe surface" rule for unsafe blocks, but the same principle applies to cargo fix. Keep the tool's scope small. Run it on a clean workspace when possible. Use --allow-dirty only when you understand the risk. It prevents accidental data loss.

Pitfalls and limits

cargo fix is not magic. It has hard limits. It only applies fixes the compiler suggests. It cannot fix logic errors. It cannot fix complex refactoring needs. It cannot fix third-party dependency issues.

If you have a macro that generates code violating the new edition, the compiler might emit an error inside the macro expansion. cargo fix cannot patch macro definitions automatically. You get a compile error. You must fix the macro manually. The tool stops and reports the error. It leaves the file alone.

Error inline: If you hit a hard error, the tool aborts. You might see a message like "fixing the edition requires fixing errors first". The tool won't touch your files until the code compiles. Fix all errors first. Then run the migration.

Also, cargo fix does not update dependencies. If a dependency drops support for the old edition, cargo fix won't help you rewrite the dependency. You need to update the dependency to a version that supports the new edition. Run cargo update to fetch newer versions. If no version supports the new edition, you must find an alternative or fork the crate.

The tool also stops if it encounters code that cannot be automatically fixed. It reports the specific errors. You must intervene manually. Review the compiler warnings after the automated pass. Some warnings might require human judgment. The tool handles the mechanical work. You handle the edge cases.

Don't fight the compiler here. Fix errors before you fix editions. A broken build blocks the migration.

Decision matrix

Use cargo fix --edition when you are upgrading your project to a newer Rust edition and want to apply automated syntax patches. Use cargo fix --edition --allow-dirty when you have uncommitted changes in your workspace and need to run the migration without stashing first. Use cargo fix --edition --workspace when you are managing a monorepo and need to migrate all member crates in a single pass. Reach for manual refactoring when the migration involves semantic changes that require human judgment, such as updating complex macro logic or adapting to breaking API changes in dependencies. Run cargo update before cargo fix --edition when your dependencies might have newer versions that support the target edition better.

Trust the tool for syntax. Trust yourself for logic. cargo fix handles the boring parts. You handle the meaning.

Where to go next