In Python you often use flake8, pyflakes and/or ruff to lint your code. In Rust, you can use Clippy.

Clippy is a collection of lints to catch common mistakes and improve your Rust code. Let's try it out on Pybites Search.

Installing and running Clippy

First make sure you install Clippy:

$ rustup component add clippy

Next you can invoke it in any project through Cargo:

$ cargo clippy

Running this in the Pybites search project we get:

 search (main) $ cargo clippy
    Checking pybites-search v0.6.0 (/Users/pybob/code/rust/search)
warning: writing `&Vec` instead of `&[_]` involves a new object where a slice will do
   --> src/main.rs:108:25
    |
108 | fn save_to_cache(items: &Vec<Item>) -> Result<(), Box<dyn std::error::Error>> {
    |                         ^^^^^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg
    = note: `#[warn(clippy::ptr_arg)]` on by default
help: change this to
    |
108 ~ fn save_to_cache(items: &[Item]) -> Result<(), Box<dyn std::error::Error>> {
109 |     let cache_path = get_cache_file_path();
110 |     let cache_data = CacheData {
111 |         timestamp: SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(),
112 ~         items: items.to_owned(),
    |

warning: `pybites-search` (bin "psearch") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.16s

The warning is about using &Vec instead of &[_]. The fix is to change the function signature to fn save_to_cache(items: &[Item]) -> Result<(), Box<dyn std::error::Error>> { (and items.clone() to items.to_vec() in the function body).

It's useful to check out the associated link where we can read about the why:

Requiring the argument to be of the specific size makes the function less useful for no benefit; slices in the form of &[T] or &str usually suffice and can be obtained from other types, too.

And a suggested fix:

fn foo(&Vec<u32>) { .. }

// use instead:

fn foo(&[u32]) { .. }

Note this is a warning, not an error, you can still compile and run your code.

But it's good practice to fix these warnings to improve the quality of your code. 🦀 🧹

Running Clippy on another project

Let's run it on the resize-images project:

warning: the borrowed expression implements the required traits
  --> src/main.rs:54:105
   |
54 |                 let output_path = Path::new(&output_dir).join(path.file_stem().unwrap()).with_extension(&extension);
   |                                                                                                         ^^^^^^^^^^ help: change this to: `extension`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrows_for_generic_args
   = note: `#[warn(clippy::needless_borrows_for_generic_args)]` on by default

warning: `resize-images` (bin "resize-images") generated 1 warning (run `cargo clippy --fix --bin "resize-images"` to apply 1 suggestion)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.81s

This time it's about a needless borrow for generic args. The fix is to remove the & from with_extension(&extension).

Again the help link explains why:

Suggests that the receiver of the expression borrows the expression.

And shows an example + fix:

fn f(_: impl AsRef<str>) {}

let x = "foo";
f(&x);

// use instead:
fn f(_: impl AsRef<str>) {}

let x = "foo";
f(x);

Auto-fixing Clippy warnings

You can also auto-fix Clippy warnings with cargo clippy --fix. This will apply the suggestions it has for you. It's a great way to quickly clean up your code.

$ cargo clippy --fix
Example of clippy autofixing an error

Running Clippy as part of pre-commit

You can run Clippy as part of your pre-commit hooks. This way you can't commit code with Clippy warnings.

If you're new to pre-commit, check out my video here

To do this, install the pre-commit tool and add the following to a .pre-commit-config.yaml file in your project (taken from here):

repos:
  - repo: https://github.com/doublify/pre-commit-rust
    rev: v1.0
    hooks:
    - id: fmt
      name: fmt
      description: Format files with cargo fmt.
      entry: cargo fmt
      language: system
      types: [rust]
      args: ["--"]
    - id: cargo-check
      name: cargo check
      description: Check the package for errors.
      entry: cargo check
      language: system
      types: [rust]
      pass_filenames: false
    - id: clippy
      name: clippy
      description: Lint rust sources
      entry: cargo clippy
      language: system
      args: ["--", "-D", "warnings"]
      types: [rust]
      pass_filenames: false

Apart from clippy, this also includes fmt and cargo check hooks.

Then install the pre-commit hooks:

$ pre-commit install

Now every time you commit code, Clippy + friends will run and you can't commit code with warnings. 🚫

To run it on all files retroactively in your project:

$ pre-commit run --all-files

I just did that and see here the result.

Imports are nicely ordered and the code is better formatted. This reminds me a lot of isort and black in Python, where Clippy is more like flake8 and pyflakes 🦀 🐍 😍

Conclusion

Clippy is a great tool to help you write better Rust code. It's easy to install and run. You can even auto-fix warnings. 💪 📈

There are many more configuration options, check out the Clippy lints and its GitHub repo for more info.

It's also convenient to run it as part of pre-commit. This way you can't commit code with warnings. It's a great way to keep your code clean and readable. 🦀 🧹