RustArena

Step 5 of 7

Clippy: fixing common lints

Clippy is Rust's official linter: hundreds of extra checks, on top of the compiler's, for code that is wrong, slow, or just not idiomatic. Every serious Rust team runs it, and most block pull requests that have clippy warnings. It comes with rustup (rustup component add clippy if it's missing):

$ cargo clippy
warning: the loop variable `i` is only used to index `prices`
 --> src/main.rs:3:14
  |
3 |     for i in 0..prices.len() {
  |              ^^^^^^^^^^^^^^^
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.97.0/index.html#needless_range_loop
  = note: `#[warn(clippy::needless_range_loop)]` on by default
help: consider using an iterator
  |
3 -     for i in 0..prices.len() {
3 +     for <item> in &prices {
  |

Each warning names its lint (needless_range_loop), links to an explanation with examples, and usually suggests the fix. cargo clippy --fix applies the suggestions it's sure about.

Lint groups

Group Default Catches
clippy::correctness error code that is almost certainly a bug
clippy::suspicious warn code that is probably a bug
clippy::style warn code that works but isn't idiomatic
clippy::complexity warn simple things done in a complicated way
clippy::perf warn code that could easily be faster
clippy::pedantic off stricter lints, with some false alarms
clippy::nursery off new lints still being polished
clippy::restriction off team rules, like banning unwrap or dbg!

The lints you'll see most

Lint Instead of Write
needless_range_loop for i in 0..v.len() { v[i] } for x in &v, or .iter().enumerate() if you need i
len_zero v.len() == 0 v.is_empty()
needless_return return x; as the last line x
ptr_arg fn f(v: &Vec<T>), fn g(s: &String) fn f(v: &[T]), fn g(s: &str)
manual_map match o { Some(x) => Some(f(x)), None => None } o.map(f)
useless_format format!("{}", s) s.to_string()
bool_comparison if done == true if done
unwrap_or_default .unwrap_or(String::new()) .unwrap_or_default()
single_match match o { Some(x) => ..., _ => {} } if let Some(x) = o { ... }
collapsible_if if a { if b { ... } } if a && b { ... }

Why &[T] over &Vec<T>? A slice parameter accepts a Vec, an array, or part of either; &Vec<T> only accepts a whole Vec, and adds an extra pointer hop. Same for &str over &String.

Here's a function with four of those warnings, and its clippy-clean version:

fn total(prices: &Vec<u32>) -> u32 {
    if prices.len() == 0 {
        return 0;
    }
    let mut sum = 0;
    for i in 0..prices.len() {
        sum += prices[i];
    }
    return sum;
}
fn total(prices: &[u32]) -> u32 {
    prices.iter().sum()
}

fn main() {
    let cart = vec![1999, 450, 300];
    println!("{}", total(&cart));
    println!("{}", total(&[]));
    println!("{}", total(&cart[1..]));
}
2749
0
750

The empty check disappeared too: the sum of nothing is already 0. And total(&cart[1..]) works only because the parameter is a slice.

(This site compiles with rustc, not clippy, so its clippy exercises use rules that reject the flagged patterns.)

Common mistakes

Your task

Rewrite total_weight so clippy has nothing to say: take a slice instead of &Vec, don't compare len() with 0, don't loop over indexes, and don't end with return. It must still return the same totals.

Challenge 2

Clippy flags display_name with unwrap_or_default, bool_comparison (== true) and useless_format (format!("{}", x)), and a reviewer adds two more: take &str instead of &String (what ptr_arg asks for), and make the body an if/else expression instead of using an early return. Rewrite it with all five fixes. It shows nick (full name), or just the full name when there's no nickname or the nickname is empty.

Challenge 3

Clippy flags this loop with needless_range_loop: i is only used for indexing. Rewrite it with iterators (zip to walk both arrays together and enumerate for the position) so it prints exactly the same:

1. ana: 30
2. bo: 45
3. cy: 12

Previous: Documenting code with rustdoc Next: Configuring clippy and rustfmt