Clippy and rustfmt: linting and formatting Rust code
How to use cargo clippy and cargo fmt: the most common clippy suggestions with before and after code, lint levels, configuration, and running both in CI.
Two tools come with every Rust installation and are used on virtually every professional Rust project:
- rustfmt (
cargo fmt) formats code in the community style: indentation, line breaks, spacing. Nobody argues about style in code review because nobody formats by hand. - clippy (
cargo clippy) is a linter with hundreds of checks for bugs, slow code, and non-idiomatic patterns, each with an explanation and usually a suggested fix.
rustfmt
$ cargo fmt # format the whole project
$ cargo fmt --check # CI: fail if anything isn't formatted
Configure it (rarely) with a rustfmt.toml, for example max_width = 120. Most teams keep the defaults. Set your editor to format on save; rust-analyzer does it for you in VS Code.
clippy's most common suggestions
| Lint | Before | After |
|---|---|---|
needless_return |
return x; as the last line |
x |
len_zero |
if v.len() == 0 |
if v.is_empty() |
needless_range_loop |
for i in 0..v.len() { v[i] ... } |
for x in &v or .iter().enumerate() |
ptr_arg |
fn f(s: &String), fn g(v: &Vec<i32>) |
fn f(s: &str), fn g(v: &[i32]) |
bool_comparison |
if done == true |
if done |
clone_on_copy |
x.clone() on an i32 |
x |
manual_map |
match opt { Some(x) => Some(f(x)), None => None } |
opt.map(f) |
collapsible_if |
if a { if b { ... } } |
if a && b { ... } |
single_match |
a match with one arm and _ => {} |
if let |
useless_format |
format!("{}", s) |
s.to_string() |
unwrap_or_default |
.unwrap_or(Vec::new()) |
.unwrap_or_default() |
map_entry |
if !m.contains_key(k) { m.insert(k, v) } |
m.entry(k).or_insert(v) |
A clippy warning looks like a compiler warning, with a link to the lint's documentation:
warning: length comparison to zero
--> src/main.rs:4:8
|
4 | if names.len() == 0 {
| ^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `names.is_empty()`
Lint groups and levels
Lints are grouped: clippy::correctness (likely bugs, deny by default), suspicious, style, complexity and perf (warn by default), plus opt-in pedantic, nursery and restriction. Adjust them:
- In code:
#[allow(clippy::too_many_arguments)]on an item, or better#[expect(clippy::lint_name, reason = "...")], which warns you when the exception is no longer needed. - Project-wide in
Cargo.toml:
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
unwrap_used = "warn"
module_name_repetitions = "allow"
In CI
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test
-D warnings turns every warning into an error, so the main branch stays clean. cargo clippy --fix applies machine-applicable suggestions automatically.
Advice
- Fix clippy warnings as you go; they're cheap when the code is fresh.
- Read the lint's explanation before silencing it. Most suggestions also make code faster or clearer.
- Don't enable all of
pedanticon an existing codebase in one go; pick the lints your team agrees on.
Example
// Idiomatic versions of the "before" patterns from the table above.
use std::collections::HashMap;
fn shout(s: &str) -> String {
s.to_uppercase()
}
fn total(values: &[i32]) -> i32 {
values.iter().sum()
}
fn first_long(words: &[&str]) -> Option<usize> {
words.iter().find(|w| w.len() > 4).map(|w| w.len())
}
fn main() {
let names: Vec<String> = Vec::new();
if names.is_empty() {
println!("no names yet");
}
let scores = vec![3, 9, 4];
for (i, s) in scores.iter().enumerate() {
println!("#{i}: {s}");
}
println!("total {}", total(&scores));
let greeting = String::from("hello");
println!("{}", shout(&greeting));
let mut counts: HashMap<&str, u32> = HashMap::new();
for word in ["a", "b", "a"] {
*counts.entry(word).or_insert(0) += 1;
}
let mut keys: Vec<_> = counts.iter().collect();
keys.sort();
println!("{keys:?}");
let empty: Option<Vec<i32>> = None;
println!("{:?} {:?}", empty.unwrap_or_default(), first_long(&["hi", "crabby"]));
}
Output:
no names yet
#0: 3
#1: 9
#2: 4
total 16
HELLO
[("a", 2), ("b", 1)]
[] Some(6)