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. ๐Ÿฆ€ ๐Ÿงน