Posted on

Table of Contents

Clippy is an essential linter for Rust, helping to catch common mistakes and improve code quality by providing insightful warnings and suggestions. By configuring Clippy with specific lints, you can enhance your skills as a Rust programmer, ensuring your code is cleaner, more efficient, and aligned with best practices. In this guide, we’ll explore several Clippy lints, including some frequently recommended by the Rust community, and demonstrate how to configure them.

Recommended Clippy Lints for Improved Rust Code

Here are some of the most recommended Clippy lints that can help you write better Rust code. Click on each lint to learn more about its purpose and how it can benefit your codebase.

  1. single_match: Warns when a single match statement can be replaced with an if let statement.
  2. single_match_else: Warns when a single match with an else clause can be replaced with an if let... else statement.
  3. needless_match: Warns when a match statement is used when an if statement would suffice.
  4. needless_late_init: Detects late initializations that can be avoided.
  5. redundant_pattern_matching: Warns when pattern matching on Option or Result is unnecessary.
  6. redundant_pattern: Detects redundant patterns in matches.
  7. redundant_guards: Warns about redundant match guards.
  8. collapsible_match: Warns about match statements that can be collapsed.
  9. match_single_binding: Warns when a match statement with a single binding can be simplified.
  10. match_same_arms: Detects match arms that contain the same code.
  11. match_ref_pats: Warns about ref patterns in match statements.
  12. match_bool: Warns about match statements over booleans.
  13. needless_bool: Denotes when a boolean expression can be simplified.
  14. unwrap_used: Warns about the use of unwrap, suggesting safer alternatives.
  15. expect_used: Warns about the use of expect, suggesting safer alternatives.

How to Add Clippy Lints to Your Rust Project?

To use these lints, you need to configure Clippy to include them in your Rust project. Here’s how you can add these lints to your Clippy configuration.

Method 1: Adding Lints via Command Line

You can run Clippy with specific warnings enabled directly from the command line. For instance:

cargo clippy -- \
    -W clippy::single_match \
    -W clippy::single_match_else \
    -W clippy::needless_match \
    -W clippy::needless_late_init \
    -W clippy::redundant_pattern_matching \
    -W clippy::redundant_pattern \
    -W clippy::redundant_guards \
    -W clippy::collapsible_match \
    -W clippy::match_single_binding \
    -W clippy::match_same_arms \
    -W clippy::match_ref_pats \
    -W clippy::match_bool \
    -D clippy::needless_bool \
    -W clippy::unwrap_used \
    -W clippy::expect_used

Method 2: Adding Lints in cargo.toml

For a more permanent setup, you can add these lints to your cargo.toml file. This ensures that Clippy will always run with these configurations whenever you use it.

In your cargo.toml, add the following under [lints.clippy]:

[lints.clippy]
single_match = "warn"
single_match_else = "warn"
needless_match = "warn"
needless_late_init = "warn"
redundant_pattern_matching = "warn"
redundant_pattern = "warn"
redundant_guards = "warn"
collapsible_match = "warn"
match_single_binding = "warn"
match_same_arms = "warn"
match_ref_pats = "warn"
match_bool = "warn"
needless_bool = "deny"
unwrap_used = "warn"
expect_used = "warn"

Method 3: Using a .cargo/config.toml File in Your Project or Home Directory

You can also create a .cargo/config.toml file in your project or home directory ~/.cargo/config to set up Clippy lints. This file in these locations allow you to respectively configure per project or globally, providing flexibility in managing lints. For more information check the Cargo Book

[target.'cfg(all())']
rustflags = [
    "-W clippy::single_match",
    "-W clippy::single_match_else",
    "-W clippy::needless_match",
    "-W clippy::needless_late_init",
    "-W clippy::redundant_pattern_matching",
    "-W clippy::redundant_pattern",
    "-W clippy::redundant_guards",
    "-W clippy::collapsible_match",
    "-W clippy::match_single_binding",
    "-W clippy::match_same_arms",
    "-W clippy::match_ref_pats",
    "-W clippy::match_bool",
    "-D clippy::needless_bool",
    "-W clippy::unwrap_used",
    "-W clippy::expect_us",
]

Understanding Key Lints by Example

Let's take a closer look at some of these lints to understand how they can help improve your Rust code.

clippy::single_match and clippy::single_match_else: These lints encourage the use of if let statements where appropriate, making the code more concise and readable.

let option = Some(42);

// Before
match option {
    Some(x) => println!("{}", x),
    _ => (),
}

// After
if let Some(x) = option {
    println!("{}", x);
}

clippy::needless_match: This lint helps replace unnecessary match statements with simpler if statements.

// Before
fn foo() -> Result<(), i32> {
    match result {
        Ok(val) => Ok(val),
        Err(err) => Err(err),
    }
}

fn bar() -> Option<i32> {
    if let Some(val) = option {
        Some(val)
    } else {
        None
    }
}

// After
fn foo() -> Result<(), i32> {
    result
}

fn bar() -> Option<i32> {
    option
}

clippy::redundant_pattern_matching and clippy::redundant_pattern: These lints catch unnecessary pattern matching and redundant patterns, suggesting more streamlined code.

// Before
if let Ok(_) = Ok::<i32, i32>(42) {}
if let Err(_) = Err::<i32, i32>(42) {}
if let None = None::<()> {}
if let Some(_) = Some(42) {}
if let Poll::Pending = Poll::Pending::<()> {}
if let Poll::Ready(_) = Poll::Ready(42) {}
if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
match Ok::<i32, i32>(42) {
    Ok(_) => true,
    Err(_) => false,
};

let cond = true;
if let true = cond {}
matches!(cond, true);

// After
if Ok::<i32, i32>(42).is_ok() {}
if Err::<i32, i32>(42).is_err() {}
if None::<()>.is_none() {}
if Some(42).is_some() {}
if Poll::Pending::<()>.is_pending() {}
if Poll::Ready(42).is_ready() {}
if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
Ok::<i32, i32>(42).is_ok();

let cond = true;
if cond {}
cond;

Looking for examples see the Clippy Lints Overview

Clippy's Lint Groups

To further enhance your Rust code quality and maintainability, have a look at Clippy's lint categories and consider enabling additional lints that suit your project's needs:

  1. pedantic: Enforces additional lints that are more pedantic, helping to catch more nuanced issues.
  2. nursery: Enables lints that are still being trialed and have not yet been stabilized but can catch emerging patterns of code issues.
  3. cargo: Checks for issues specifically related to Cargo, such as unused dependencies.
  4. complexity: Warns about overly complex code that may be simplified.
  5. style: Focuses on stylistic issues that can improve code readability and consistency. Beaware that these lints are opinionated.
  6. perf: Identifies performance issues in the code. Sincerely, this is a must-have for performance-critical applications.
  7. correctness: Ensures that the code is correct and free from common mistakes.
  8. restriction: Applies stricter rules that can help enforce certain coding standards within a project.
  9. suspicious: Detects suspicious code patterns that may indicate potential bugs or issues.

If you enable more lints, you may need to set priorities because certain lints may conflict with each other. See Cargo Book - Lint section for more information.

Conclusion

By incorporating these Clippy lints into your workflow, you can ensure your Rust code is not only functional but also idiomatic, efficient, and maintainable. Experiment with different lints and configurations to find the right balance for your project, and watch your code quality soar to new heights.

References: