Clippy & rustfmt
Two tools ship with every standard Rust installation and together enforce both correctness and consistency: Clippy catches common mistakes and suggests more idiomatic code, while rustfmt formats your source code according to the community style guide. Using both in CI means code review conversations focus on logic rather than lint or style debates.
What is Clippy?
Clippy is a collection of over 700 lints that analyse your Rust code for potential bugs, unnecessary complexity, and non-idiomatic patterns. It goes well beyond what the compiler reports — many Clippy lints catch real bugs in production code.
# install (included in the default toolchain) rustup component add clippy # run Clippy on your project cargo clippy
warning: called `unwrap` on `Option` value
--> src/main.rs:12:5
|
12 | config.get("host").unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: consider using `expect` or handling the `None` case explicitly
= note: `#[warn(clippy::unwrap_used)]` on by defaultLint Categories
Clippy organises its lints into categories so you can understand the risk level of each warning at a glance.
Category | What it catches | Default |
|---|---|---|
correctness | Almost certainly a bug — code that is wrong | deny (error) |
suspicious | Probably wrong or confusing | warn |
style | Non-idiomatic Rust; cleaner ways to write the same thing | warn |
complexity | Unnecessarily complex code | warn |
performance | Patterns that are slower than they need to be | warn |
pedantic | Extra strictness — off by default | allow |
nursery | Work-in-progress lints — may have false positives | allow |
Useful Clippy Lints in Practice
The following lints come up frequently in real projects and are worth understanding individually.
// clippy::unwrap_used — warns when you call .unwrap()
fn get_name(map: &std::collections::HashMap<&str, &str>) -> &str {
map.get("name").unwrap() // Clippy warns here
// prefer: map.get("name").unwrap_or("unknown")
}
// clippy::expect_used — warns when you call .expect()
fn read_port(env: &str) -> u16 {
env.parse().expect("PORT must be a number") // Clippy warns here
}
// clippy::map_unwrap_or — suggests a cleaner alternative
fn double_if_some(opt: Option<i32>) -> i32 {
opt.map(|n| n * 2).unwrap_or(0) // fine
// clippy prefers: opt.map_or(0, |n| n * 2)
}
// clippy::vec_init_then_push — use vec! macro instead
fn build_list() -> Vec<i32> {
let mut v = Vec::new();
v.push(1); // Clippy suggests: let v = vec![1, 2, 3];
v.push(2);
v.push(3);
v
}
// clippy::clone_on_ref_ptr — cloning an Arc/Rc is cheap but
// Clippy can remind you to use Arc::clone(&ptr) for clarity
use std::sync::Arc;
fn clone_arc(ptr: &Arc<String>) -> Arc<String> {
ptr.clone() // works, but
// Arc::clone(ptr) // is more explicit about what's happening
}Allowing Specific Lints
Sometimes a Clippy warning does not apply to your specific situation. You can suppress
individual lints with the #[allow] attribute placed on the item or block in question.
// Suppress for a single function
#[allow(clippy::unwrap_used)]
fn legacy_code() {
let value = std::env::var("KEY").unwrap();
println!("{}", value);
}
// Suppress for a single expression
fn process() {
#[allow(clippy::vec_init_then_push)]
let mut items = Vec::new();
items.push(1);
items.push(2);
}#![allow(...)]. Wide suppressions hide future occurrences of the same mistake.Denying Lints in CI
The most common CI pattern is to treat every Clippy warning as a hard error. This ensures the codebase never accumulates unchecked warnings.
# Fail CI if any Clippy warning is produced cargo clippy -- -D warnings # Also enable pedantic lints cargo clippy -- -D warnings -W clippy::pedantic
# Example GitHub Actions step - name: Clippy run: cargo clippy -- -D warnings
-D clippy::pedantic on an existing project will produce many warnings. Enable it incrementally, project by project, and suppress specific pedantic lints you disagree with using#[allow].What is rustfmt?
rustfmt is the official Rust code formatter. It enforces a consistent, community-agreed style across your entire codebase — indentation, line breaks, spacing, import ordering, and more. Because rustfmt is deterministic, two developers can independently format the same file and get identical output.
# install (included in the default toolchain) rustup component add rustfmt # format all files in the project cargo fmt # check formatting without modifying files (for CI) cargo fmt --check
Diff in /home/user/project/src/main.rs:
let x=1;
+ let x = 1;Configuring rustfmt
Place a rustfmt.toml (or .rustfmt.toml) file at the root of your project to
customise formatter behaviour. Most teams accept the defaults, but common tweaks
include line width and import grouping.
# rustfmt.toml edition = "2021" max_width = 100 # default is 100; increase for wide monitors tab_spaces = 4 # default; 4-space indentation imports_granularity = "Crate" # group imports by crate group_imports = "StdExternalCrate" # std, then external, then local
unstable_features = true to rustfmt.toml and using a nightly toolchain. Stick to stable options in shared codebases.Why Consistent Formatting Matters
Eliminates style debates in code review — reviewers focus on logic, not whether there should be a space before a brace
Reduces merge conflicts — two developers format identically, so their diffs don't collide on whitespace
Lowers the mental load of context switching — all Rust code looks the same, whether it's in your project or an external crate
Speeds up onboarding — new contributors don't need to learn project-specific style rules
rustfmt in Your Editor
The rust-analyzer extension (available for VS Code, Neovim, Emacs, and most other editors) integrates rustfmt natively. Enable format-on-save so your file is always formatted the moment you save it.
// VS Code settings.json
{
"editor.formatOnSave": true,
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
}
}Opting Out of Formatting
Occasionally a piece of code is more readable in a hand-crafted layout (such as a
carefully aligned table of constants). Use #[rustfmt::skip] to tell the formatter
to leave that item alone.
#[rustfmt::skip]
const LOOKUP: [u8; 8] = [
0x00, 0x01,
0x10, 0x11,
0x20, 0x21,
0x30, 0x31,
];cargo fmt and cargo clippy -- -D warnings as part of every CI pipeline. Together they ensure your codebase stays idiomatic, readable, and free of easily preventable bugs — without any manual review overhead.