RustArena

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            # 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:

[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

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)

Practice it