The tooling that isn't a plugin
You come from a world where you install extensions to make your build tool do magic. You search for "Rust plugins" and find a wall of text about cargo subcommands. It feels like the tooling is missing. It isn't. Rust just organizes its extensions differently. You don't load plugins into the compiler. You chain tools together. The result is a workflow that feels lighter, faster, and harder to break.
Rust has no plugin API. There is no cargo register_plugin. There is no version lockstep between the compiler and the formatter. The entire "plugin" ecosystem is just a naming convention. If you install a binary named cargo-<name>, typing cargo <name> runs it. That is the whole contract. No registration. No hooks. Just a binary on your PATH.
This design has a surprising consequence. You don't need to know Rust to write a Cargo tool. You can write cargo-coverage in Python. You can write cargo-deps in JavaScript. As long as the binary is named cargo-<name> and is executable, Cargo runs it. The ecosystem is polyglot by accident. Tools update independently of the compiler. You get the latest linter without waiting for a Rust release. You get a new formatter without recompiling the standard library.
How Cargo finds your tools
Cargo acts as a shell runner that understands Rust projects. When you type cargo fmt, Cargo doesn't have built-in logic for formatting. It searches your PATH for an executable named cargo-fmt. If it finds one, it runs it. If it doesn't, it gives up.
This means tool installation falls into two buckets. Some tools ship with the official toolchain. You install them with rustup. Other tools live on crates.io. You install them with cargo install. The distinction matters for updates and versioning.
# Install toolchain components. These stay in sync with your compiler.
rustup component add rustfmt
rustup component add clippy
# Install third-party tools. These update independently.
cargo install cargo-geiger
cargo install cargo-audit
# Run them. Cargo discovers the binaries automatically.
cargo fmt
cargo clippy
cargo geiger
The rustup command manages the compiler and its companions. rustfmt and clippy are companions. They are tested against the compiler version. When you switch Rust versions, rustup switches these tools too. This prevents version drift. You never run a formatter designed for Rust 1.60 against code compiled by Rust 1.70.
Third-party tools use cargo install. This downloads the source from crates.io, compiles it, and drops the binary into ~/.cargo/bin. You control the version. You update when you want. This is great for flexibility but requires you to watch for breaking changes.
The big three for daily work
Three tools dominate the Rust workflow. cargo fmt handles formatting. cargo clippy handles linting. rust-analyzer handles IDE support. They cover the gap between writing code and shipping code.
Formatting with cargo fmt
cargo fmt is the community standard for code style. It reformats your source files to match a strict set of rules. There is no debate. If you submit a pull request without formatting, maintainers will reject it. Not because they are mean, but because formatting diffs clutter the review. cargo fmt makes diffs about logic, not whitespace.
/// Calculates the average of a slice of numbers.
/// Clippy will suggest using `is_empty()` instead of this check.
fn average(values: &[f64]) -> f64 {
if values.len() == 0 {
return 0.0;
}
let sum: f64 = values.iter().sum();
sum / values.len() as f64
}
Run cargo fmt on this code. The indentation changes. The spacing changes. The logic stays the same. The output is deterministic. Every developer on the team gets the exact same result. This ends style wars. You stop arguing about tabs versus spaces. You stop arguing about brace placement. You run the formatter and move on.
Convention aside: rustfmt.toml exists. You can configure the formatter. Don't. The default configuration is the convention. Only configure if you have a strong reason. If you configure it, commit the config file. Otherwise, your local formatter disagrees with the CI formatter, and the build breaks.
Linting with cargo clippy
The compiler checks correctness. clippy checks idioms. You might write code that compiles but looks weird. Clippy will tell you. It catches patterns that are error-prone, inefficient, or non-idiomatic.
/// Checks if a value is present in an option.
/// Clippy suggests using `is_some()` instead of this match.
fn is_present(value: Option<i32>) -> bool {
match value {
Some(_) => true,
None => false,
}
}
Run cargo clippy. You get a warning.
warning: this
matchexpression can be replaced with.is_some()--> src/lib.rs:4:5 | 4 | / match value { 5 | | Some(_) => true, 6 | | None => false, 7 | | } | |_____^ help: try this:value.is_some()| = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro
Clippy isn't just a style checker. It catches bugs. It finds map followed by unwrap. It finds unnecessary clones. It finds integer overflow risks. It acts like a senior reviewer who never sleeps.
You can silence clippy lints. Use #[allow(clippy::lint_name)]. Use this sparingly. If you silence a lint, add a comment explaining why. Future you will thank present you.
/// Intentionally verbose for clarity in this example.
/// We silence the lint because the match makes the logic explicit for beginners.
#[allow(clippy::match_like_matches_macro)]
fn is_present_verbose(value: Option<i32>) -> bool {
match value {
Some(_) => true,
None => false,
}
}
Convention aside: In CI pipelines, run cargo clippy -- -D warnings. The -- passes arguments to clippy. -D warnings turns warnings into errors. This ensures that a new clippy warning breaks the build. You catch idiomatic issues before they land in main.
IDE support with rust-analyzer
rust-analyzer is different. It is not a cargo subcommand. It is a language server. It runs in the background and talks to your editor over JSON. It provides go-to-definition, autocomplete, inline errors, and refactoring.
You don't run rust-analyzer from the terminal. Your editor runs it. VS Code, Neovim, Emacs, and IntelliJ all support it. You install the extension or configure the plugin. The editor launches the server. The server parses your code and answers queries.
rust-analyzer is fast because it doesn't compile. It builds a semantic model of your code without running the full compiler pipeline. It understands types and lifetimes instantly. This is why autocomplete works while you type. It doesn't wait for cargo check. It knows what you mean.
If you forget to install rust-analyzer, your editor is dumb. You get syntax highlighting but no intelligence. You can't jump to definitions. You can't see types on hover. Install it. Your productivity jumps immediately.
Pitfalls and errors
Tooling breaks in predictable ways. Knowing the errors saves time.
If you try to run cargo fmt and haven't installed the component, you get:
error: no such subcommand: 'fmt'
The compiler isn't broken. You just need the component. Run rustup component add rustfmt. The error goes away.
If you install a third-party tool with cargo install and the command isn't found, your PATH is wrong. Cargo installs binaries to ~/.cargo/bin. This directory must be on your PATH. Add export PATH="$HOME/.cargo/bin:$PATH" to your shell config. Restart your terminal. The tool appears.
If rust-analyzer shows errors that the compiler doesn't, the server might be out of sync. Restart the server. In VS Code, use the command palette to restart rust-analyzer. In Neovim, reload the LSP client. The server rebuilds its index. The errors vanish.
If clippy complains about a lint that you think is wrong, check the lint documentation. Some lints are pedantic. You can allow them. Don't ignore them blindly. Clippy usually has a point.
When to use what
Use rustup component add when the tool ships with the official toolchain, like rustfmt, clippy, or rust-src. These stay in sync with your compiler version. Use cargo install when you want a third-party tool from crates.io, like cargo-audit or cargo-geiger. These update independently and don't require a toolchain switch. Use rust-analyzer when you need IDE features like go-to-definition, autocomplete, and inline errors. It runs as a language server, not a cargo subcommand, and provides the smart editing experience.
Treat the toolchain as a bundle. If you switch Rust versions, your tools switch with it. Run cargo fmt before every commit. Your teammates will love you. Trust clippy. It usually has a point.