Pedantic Clippy
One of the reasons developers love Rust is its well-developed ecosystem. Clippy, a linter for the Rust code, is one of the main components in this ecosystem. It performs additional checks of the developed code reporting found issues and explaining how to fix them (and sometimes it even can fix them automatically). Its usage may be beneficial for Rust beginners and even professionals. In this article, I describe this tool and explain how to start using it.
Table of Contents
Introduction
Theory and practice should go hand in hand when you learn new things. That is why, when you learn new programming language, e.g., Rust, you should try the examples you read about. At the same time, you should not only repeat them but also change some things and see, how this influences the behavior of the compiler and your program. It is highly beneficial if during these experiments someone points on the issues in your code and explains how to fix them. This is where clippy shines. The usage of this tool may be very advantageous if you try to learn Rust on your own, without an external mentor, because it may teach you how to avoid common issues and write more idiomatic code. To my bad, I have discovered this tool just recently, and I decided to spend some time to understand how it works and how to use it.
First of all, in order to start using this tool you need to check if it is installed in your system. I have just checked on my Linux system, the instructions explaining how to install Rust also install clippy on your system by default. If you have already installed Rust and you use Linux, go to your $HOME/.cargo/bin
directory (default directory where cargo binaries are installed) and check if it contains cargo-clippy
(the path on Windows-based systems is different). If it is there, then clippy is installed on your system and ready to go. Also, you can try to execute cargo clippy -V
in console and check the output. If the command exits correctly and reports clippy version, then your system is ready:
$ cargo clippy -V
clippy 0.1.54 (a178d03 2021-07-26)
If clippy is not present in your system, then try to install it using rustup
:
$ rustup component add clippy
Example
In order to show that clippy improves your code, let’s consider the following completely artificial example:
use std::env;
fn equal_to_approx_pi(x: f32) -> bool {
const PI: f32 = 3.14;
if x == PI {
return true;
}
return false;
}
fn main() {
const PI: f32 = 3.14;
println!("The approximate value of PI is {}", PI);
let submitted_value = env::args()
.nth(1).unwrap()
.parse().unwrap();
let equal = equal_to_approx_pi(submitted_value);
if equal == true {
println!("Equal!");
} else {
println!("Not equal!");
}
}
This binary parses the first argument into a 32-bit float number, and compares it to approximate $\pi$ value in the equal_to_approx_pi
function. If the value is equal to the approximate $\pi$ value, the binary prints Equal!
and Not equal!
otherwise. Note, in this example we simply use the unwrap
method to get wrapped values without processing possible errors.
If we try to compile this program, the compiler will build it successfully without any error or warning:
$ cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
This means that from the compiler point of view this is a correct program. But is it from a developer point of view? Obviously not, and clippy can help you to identify some “stinky” parts in your code (I put outputs under the spoiler tag, just click on the following line to see the output):
Clippy Output
$ cargo clippy
Checking clippy_test v0.1.0 (/home/yury/PROJECTS/TESTS/RUST/clippy_test)
warning: unneeded `return` statement
--> src/main.rs:8:5
|
8 | return false;
| ^^^^^^^^^^^^^ help: remove `return`: `false`
|
= note: `#[warn(clippy::needless_return)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:4:21
|
4 | const PI: f32 = 3.14;
| ^^^^
|
= note: `#[deny(clippy::approx_constant)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:12:21
|
12 | const PI: f32 = 3.14;
| ^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
warning: equality checks against true are unnecessary
--> src/main.rs:19:8
|
19 | if equal == true {
| ^^^^^^^^^^^^^ help: try simplifying it as shown: `equal`
|
= note: `#[warn(clippy::bool_comparison)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
error: aborting due to 2 previous errors; 2 warnings emitted
error: could not compile `clippy_test`
To learn more, run the command again with --verbose.
As you can see, even in this short listing clippy was able to spot three parts that “smell” – imagine how many errors it may find in a mid-size project.
Clippy Lints
Currently, clippy contains more than 450 different lints (according to my calculations, 491 as of September, 2021). One can say that this is not a large number comparing to the amount of checks in static analyzers for other programming languages, however due to the active community, this list constantly grows, and the tool becomes better with every iteration.
Every lint is assigned with a level that controls how clippy reacts if an issue, described by the lint, is found. There are three levels:
allow
- clippy does not complain;warning
- clippy issues and displays a warning, the clippy process exits with zero success code;deny
- clippy issues and displays an error, the clippy process exits with non-zero error code.
All the lints are divided into several categories, e.g., clippy::correctness
or clippy::style
. These categories are usually used to change the lints level in bulk instead of listing each of them. Semantically, they unite lints related to some set of similar issues. Table 1 contains the list of lints categories with the description, amount of lints (as of September, 2021) and their level (taken partially from the README file):
Category | Level | Amount | Description |
---|---|---|---|
clippy::correctness | deny | 68 | Code that is outright wrong or useless |
clippy::suspicious | warn | 14 | Code that is most likely wrong or useless |
clippy::style | warn | 108 | Code that should be written in a more idiomatic way |
clippy::complexity | warn | 88 | Code that does something simple but in a complex way |
clippy::perf | warn | 21 | Code that can be written to run faster |
clippy::pedantic | allow | 92 | Lints which are rather strict or might have false positives |
clippy::restriction | allow | 57 | Lints that apply additional restrictions on the code |
clippy::nursery | allow | 22 | New lints that are still under development |
clippy::cargo | allow | 5 | Lints for the cargo manifest |
clippy::deprecated | none | 16 | Lints that already deprecated and should not be used |
There is one special category, clippy::all
, that unites all the lints enabled by default. Clippy checks a crate against clippy::all
lints if you run it without additional configurations. The example, considered in the previous section, exemplifies exactly this.
Lints Configuration
Some of the lints can be additionally configured. For instance, using the configuration you can set the list of blacklisted names, or change the maximum cognitive complexity level if your code is quite complex. Table 2 contain the list of all configuration values with their descriptions, types and in what lints they are used (as of September, 2021):
Value / Description | Type | Lints |
---|---|---|
array-size-threshold The maximum allowed size for arrays on the stack (defaults to `512000`) | u64 | large_stack_arrays large_const_arrays |
avoid-breaking-exported-api Suppress lints whenever the suggested change would cause breakage for other crates. (defaults to `true`) | bool | upper_case_acronyms unnecessary_wraps box_vec trivially_copy_pass_by_ref large_types_passed_by_value option_option enum_variant_names rc_buffer redundant_allocation wrong_self_convention vec_box linkedlist rc_mutex |
blacklisted-names The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses (defaults to `["foo", "baz", "quux"]`) | Vec<String> | blacklisted_name |
cognitive-complexity-threshold The maximum cognitive complexity a function can have (defaults to `25`) | u64 | cognitive_complexity |
disallowed-methods The list of disallowed methods, written as fully qualified paths. (defaults to `[]`) | Vec<String> | disallowed_method |
disallowed-types The list of disallowed types, written as fully qualified paths. (defaults to `[]`) | Vec<String> | disallowed_type |
doc-valid-idents The list of words this lint should not consider as identifiers needing ticks (defaults to `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`) | Vec<String> | doc_markdown |
enforced-import-renames :utils::conf::Rename>`: The list of imports to always rename, a fully qualified path followed by the rename. (defaults to `[]`) | Vec<crat | missing_enforced_import_renames |
enum-variant-name-threshold The minimum number of enum variants for the lints about variant names to trigger (defaults to `3`) | u64 | enum_variant_names |
enum-variant-size-threshold The maximum size of an enum's variant to avoid box suggestion (defaults to `200`) | u64 | large_enum_variant |
literal-representation-threshold The lower bound for linting decimal literals (defaults to `16384`) | u64 | decimal_literal_representation |
max-fn-params-bools The maximum number of bool parameters a function can have (defaults to `3`) | u64 | fn_params_excessive_bools |
max-struct-bools The maximum number of bool fields a struct can have (defaults to `3`) | u64 | struct_excessive_bools |
max-trait-bounds The maximum number of bounds a trait can have to be linted (defaults to `3`) | u64 | type_repetition_in_bounds |
msrv The minimum rust version that the project supports (defaults to `None`) | Option<String> | missing_const_for_fn redundant_field_names approx_constant filter_map_next checked_conversions manual_strip use_self unnested_or_patterns from_over_into map_unwrap_or manual_range_contains mem_replace_with_default option_as_ref_deref manual_non_exhaustive if_then_some_else_none redundant_static_lifetimes match_like_matches_macro cloned_instead_of_copied ptr_as_ptr manual_split_once manual_str_repeat |
single-char-binding-names-threshold The maximum number of single char bindings a scope may have (defaults to `4`) | u64 | many_single_char_names |
standard-macro-braces :nonstandard_macro_braces::MacroMatcher>`: Enforce the named macros always use the braces specified. | Vec<crat | nonstandard_macro_braces |
too-large-for-stack The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap (defaults to `200`) | u64 | useless_vec boxed_local |
too-many-arguments-threshold The maximum number of argument a function or method can have (defaults to `7`) | u64 | too_many_arguments |
too-many-lines-threshold The maximum number of lines a function or method can have (defaults to `100`) | u64 | too_many_lines |
trivial-copy-size-limit The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. (defaults to `None`) | Option<u64> | trivially_copy_pass_by_ref |
type-complexity-threshold The maximum complexity a type can have (defaults to `250`) | u64 | type_complexity |
unreadable-literal-lint-fractions Should the fraction of a decimal be linted to include separators. (defaults to `true`) | bool | unreadable_literal |
upper-case-acronyms-aggressive Enables verbose mode. Triggers if there is more than one uppercase char next to each other (defaults to `false`) | bool | upper_case_acronyms |
vec-box-size-threshold The size of the boxed type in bytes, where boxing in a `Vec` is allowed (defaults to `4096`) | u64 | vec_box |
verbose-bit-mask-threshold The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' (defaults to `1`) | u64 | verbose_bit_mask |
warn-on-all-wildcard-imports Whether to allow certain wildcard imports (prelude, super in tests). (defaults to `false`) | bool | wildcard_imports |
This additional configuration is specified in the clippy.toml
or .clippy.toml
files located in the root of your project in the form variable = value
:
blacklisted-names = ["toto", "tata", "titi"]
cognitive-complexity-threshold = 30
Changing Clippy Behavior
You can influence on clippy behavior by adjusting the level of clippy lints. For instance, you may want to enable lints that are currently in development, or you may want to disable some particular checks. There are two ways of doing this:
- By modifying crate’s root source code file. Rust provides a set of attributes (
allow
,warn
anddeny
) that you can use in the crate’s root. - Through command line arguments. If you do not want to change source code, you can run clippy with special command line arguments that would enable (or disable) some lints.
Let’s consider these two options.
Source Code Based Approach
You can modify the level of a category or a particular lint on the crate level. In order to do this, in the crate’s main file (main.rs
or lib.rs
) add the attributes (allow
, warn
and deny
) in the preamble (note the exclamation mark !
in the attribute). For instance, the following attribute, added in the beginning of the main.rs
file, sets the level of cargo manifest lints (clippy::cargo
category) to warn
and allows the usage of approximate constants (clippy::approx_constant
) withing the crate:
#![warn(clippy::cargo)]
#![allow(clippy::approx_constant)]
use std::env;
fn equal_to_approx_pi(x: f32) -> bool {
...
Now, if you run the cargo clippy
command you should see clippy complaining on the issues in the cargo manifest file Cargo.toml
, while “forgetting” the lint checking the usage of approximate constants:
Clippy Output
$ cargo clippy
Checking clippy_test v0.1.0 (/home/yury/PROJECTS/TESTS/RUST/clippy_test)
warning: package `clippy_test` is missing `package.description` metadata
|
note: the lint level is defined here
--> src/main.rs:1:9
|
1 | #![warn(clippy::cargo)]
| ^^^^^^^^^^^^^
= note: `#[warn(clippy::cargo_common_metadata)]` implied by `#[warn(clippy::cargo)]`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: package `clippy_test` is missing `either package.license or package.license_file` metadata
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: package `clippy_test` is missing `package.repository` metadata
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: package `clippy_test` is missing `package.readme` metadata
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: package `clippy_test` is missing `package.keywords` metadata
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: package `clippy_test` is missing `package.categories` metadata
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: unneeded `return` statement
--> src/main.rs:11:5
|
11 | return false;
| ^^^^^^^^^^^^^ help: remove `return`: `false`
|
= note: `#[warn(clippy::needless_return)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
warning: equality checks against true are unnecessary
--> src/main.rs:22:8
|
22 | if equal == true {
| ^^^^^^^^^^^^^ help: try simplifying it as shown: `equal`
|
= note: `#[warn(clippy::bool_comparison)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
warning: 8 warnings emitted
Finished dev [unoptimized + debuginfo] target(s) in 0.34s
Command Line Based Approach
Sometimes, changing the source code is not an option. For instance, you may just need to check an external project. In this case, the alternative of adjusting lints’ levels through clippy’s command line arguments may be preferable. In order to enable/disable some categories or individual lints checks for your crate, run clippy with -A
(means allow), -W
(warn) and -D
(deny) command line argument providing the corresponding category/lint after it. For instance, the same effect as in the previous example can be achieved with the following command:
$ cargo clippy -- -W clippy::cargo -A clippy::approx_constant
So as we run clippy using cargo, we pass the command line arguments to clippy after double dash --
. The arguments before double dash will be passed to cargo and thus, most probably, result in an error.
Note that the source code based configuration has a precedence over the command line based. For instance, if you set in the source code of our example the level of the lint checking the usage of approximate constants to deny
, while allowing it through the command line arguments, clippy will report an error. This is pretty odd and should be taken into the account because to my knowledge, command line arguments usually have a precedence over the source code default configuration (if you can explain this behavior, please drop a comment).
Paranoid Clippy
With this knowledge, it should be clear now how to make clippy completely paranoic:
#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![warn(clippy::restriction)]
#![warn(clippy::nursery)]
#![warn(clippy::cargo)]
use std::env;
...
For clippy::all
I set the deny
level, other lint categories are warn
, however, you can adjust those levels according to your needs. The resulting output is getting pretty long with 5 errors and 16 warnings emitted:
Clippy Output
$ cargo clippy
Checking clippy_test v0.1.0 (/home/yury/PROJECTS/TESTS/RUST/clippy_test)
warning: use of `println!`
--> src/main.rs:19:5
|
19 | println!("The approximate value of PI is {}", PI);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> src/main.rs:3:9
|
3 | #![warn(clippy::restriction)]
| ^^^^^^^^^^^^^^^^^^^
= note: `#[warn(clippy::print_stdout)]` implied by `#[warn(clippy::restriction)]`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout
warning: use of `println!`
--> src/main.rs:26:9
|
26 | println!("Equal!");
| ^^^^^^^^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout
warning: use of `println!`
--> src/main.rs:28:9
|
28 | println!("Not equal!");
| ^^^^^^^^^^^^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout
warning: missing documentation for the crate
--> src/main.rs:1:1
|
1 | / #![deny(clippy::all)]
2 | | #![warn(clippy::pedantic)]
3 | | #![warn(clippy::restriction)]
4 | | #![warn(clippy::nursery)]
... |
29 | | }
30 | | }
| |_^
|
note: the lint level is defined here
--> src/main.rs:3:9
|
3 | #![warn(clippy::restriction)]
| ^^^^^^^^^^^^^^^^^^^
= note: `#[warn(clippy::missing_docs_in_private_items)]` implied by `#[warn(clippy::restriction)]`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
warning: package `clippy_test` is missing `package.description` metadata
|
note: the lint level is defined here
--> src/main.rs:5:9
|
5 | #![warn(clippy::cargo)]
| ^^^^^^^^^^^^^
= note: `#[warn(clippy::cargo_common_metadata)]` implied by `#[warn(clippy::cargo)]`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: package `clippy_test` is missing `either package.license or package.license_file` metadata
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: package `clippy_test` is missing `package.repository` metadata
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: package `clippy_test` is missing `package.readme` metadata
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: package `clippy_test` is missing `package.keywords` metadata
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: package `clippy_test` is missing `package.categories` metadata
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
warning: missing documentation for a function
--> src/main.rs:9:1
|
9 | / fn equal_to_approx_pi(x: f32) -> bool {
10 | | const PI: f32 = 3.14;
11 | | if x == PI {
12 | | return true;
13 | | }
14 | | return false;
15 | | }
| |_^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
error: unneeded `return` statement
--> src/main.rs:14:5
|
14 | return false;
| ^^^^^^^^^^^^^ help: remove `return`: `false`
|
note: the lint level is defined here
--> src/main.rs:1:9
|
1 | #![deny(clippy::all)]
| ^^^^^^^^^^^
= note: `#[deny(clippy::needless_return)]` implied by `#[deny(clippy::all)]`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
warning: missing documentation for a constant
--> src/main.rs:10:5
|
10 | const PI: f32 = 3.14;
| ^^^^^^^^^^^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:10:21
|
10 | const PI: f32 = 3.14;
| ^^^^
|
note: the lint level is defined here
--> src/main.rs:1:9
|
1 | #![deny(clippy::all)]
| ^^^^^^^^^^^
= note: `#[deny(clippy::approx_constant)]` implied by `#[deny(clippy::all)]`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
warning: strict comparison of `f32` or `f64` constant
--> src/main.rs:11:8
|
11 | if x == PI {
| ^^^^^^^ help: consider comparing them within some margin of error: `(x - PI).abs() < error_margin`
|
note: the lint level is defined here
--> src/main.rs:3:9
|
3 | #![warn(clippy::restriction)]
| ^^^^^^^^^^^^^^^^^^^
= note: `#[warn(clippy::float_cmp_const)]` implied by `#[warn(clippy::restriction)]`
= note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
warning: missing documentation for a constant
--> src/main.rs:18:5
|
18 | const PI: f32 = 3.14;
| ^^^^^^^^^^^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:18:21
|
18 | const PI: f32 = 3.14;
| ^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
warning: used `unwrap()` on `a Result` value
--> src/main.rs:21:27
|
21 | let submitted_value = env::args()
| ___________________________^
22 | | .nth(1).unwrap()
23 | | .parse().unwrap();
| |_________________________^
|
note: the lint level is defined here
--> src/main.rs:3:9
|
3 | #![warn(clippy::restriction)]
| ^^^^^^^^^^^^^^^^^^^
= note: `#[warn(clippy::unwrap_used)]` implied by `#[warn(clippy::restriction)]`
= help: if you don't want to handle the `Err` case gracefully, consider using `expect()` to provide a better panic message
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
warning: used `unwrap()` on `an Option` value
--> src/main.rs:21:27
|
21 | let submitted_value = env::args()
| ___________________________^
22 | | .nth(1).unwrap()
| |________________________^
|
= help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
error: equality checks against true are unnecessary
--> src/main.rs:25:8
|
25 | if equal == true {
| ^^^^^^^^^^^^^ help: try simplifying it as shown: `equal`
|
note: the lint level is defined here
--> src/main.rs:1:9
|
1 | #![deny(clippy::all)]
| ^^^^^^^^^^^
= note: `#[deny(clippy::bool_comparison)]` implied by `#[deny(clippy::all)]`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
error: restriction lints are not meant to be all enabled
--> src/main.rs:3:9
|
3 | #![warn(clippy::restriction)]
| ^^^^^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> src/main.rs:1:9
|
1 | #![deny(clippy::all)]
| ^^^^^^^^^^^
= note: `#[deny(clippy::blanket_clippy_restriction_lints)]` implied by `#[deny(clippy::all)]`
= help: try enabling only the lints you really need
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints
error: aborting due to 5 previous errors; 16 warnings emitted
error: could not compile `clippy_test`
To learn more, run the command again with --verbose.
Combatting False Positives
Clearly, clippy is not perfect, and from time to time it produces false positives. In this case, you have two options: either you refactor your code (if it is possible) to make clippy happy, or you disable some checks. In the previous section, we showed how to disable a particular lint on the crate level. However, this approach is too coarse-grained, and sometimes you are required to disable a lint for a particular module or function. Luckily, clippy provides possibility to achieve this goal. In order to do this, you have to add the attribute (allow
to disable the corresponding lint) in front of the corresponding module/function. Note that in this case you should not use the !
sign. Similarly, clippy also provides a possibility to enable a lint for a particular module/function. For instance, in the following example I show allow the usage of approximate constants in the equal_to_approx_pi
function, while its usage is still prohibited in the main
function.
use std::env;
#[allow(clippy::approx_constant)]
fn equal_to_approx_pi(x: f32) -> bool {
...
Below, you can see the output. Note that we have disabled the check only for equal_to_approx_pi
; clippy still complains on the usage of approximate constants in the main
function.
Clippy Output
$ cargo clippy
Checking clippy_test v0.1.0 (/home/yury/PROJECTS/TESTS/RUST/clippy_test)
warning: unneeded `return` statement
--> src/main.rs:9:5
|
9 | return false;
| ^^^^^^^^^^^^^ help: remove `return`: `false`
|
= note: `#[warn(clippy::needless_return)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:13:21
|
13 | const PI: f32 = 3.14;
| ^^^^
|
= note: `#[deny(clippy::approx_constant)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
warning: equality checks against true are unnecessary
--> src/main.rs:20:8
|
20 | if equal == true {
| ^^^^^^^^^^^^^ help: try simplifying it as shown: `equal`
|
= note: `#[warn(clippy::bool_comparison)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
error: aborting due to previous error; 2 warnings emitted
error: could not compile `clippy_test`
To learn more, run the command again with --verbose.
Automatically Fixing Issues
Clippy is able to fix some issues automatically. The special --fix
command line flag is responsible for enabling this feature. However, this feature as of now (September, 2021) is available only for the nightly toolchain version. If you run the command on a stable channel version, you will get the following message:
$ cargo clippy --fix
thread 'main' panicked at 'Usage of `--fix` requires `-Z unstable-options`', src/tools/clippy/src/main.rs:92:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Thus, in order to use this feature, you have at first install the toolchain from the nightly channel. You can do this using the following command:
$ rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)
$ rustup toolchain install nightly
info: syncing channel updates for 'nightly-x86_64-unknown-linux-gnu'
info: latest update on 2021-09-06, rust version 1.57.0-nightly (e30b68353 2021-09-05)
info: downloading component 'cargo'
info: downloading component 'clippy'
...
$ rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu
As you can see after executing this command, you have two toolchains installed in your system: stable (default) and nightly. After nightly toolchain is installed in your system, you have two options how to activate it:
- You can set the nightly toolchain as default globally in your system.
- You can override the toolchain only for the current project.
- You can directly run clippy from nightly channel using special flags.
If you would like to switch your system to the nightly channel toolchain, use the following command:
$ rustup default nightly
info: using existing install for 'nightly-x86_64-unknown-linux-gnu'
info: default toolchain set to 'nightly-x86_64-unknown-linux-gnu'
nightly-x86_64-unknown-linux-gnu unchanged - rustc 1.57.0-nightly (e30b68353 2021-09-05)
$ rustup toolchain list
stable-x86_64-unknown-linux-gnu
nightly-x86_64-unknown-linux-gnu (default)
So as in my projects I prefer to rely on stable features, I prefer the second approach – overriding the toolchain only for your current project. In order to do this, change directory to your project and execute the following command:
$ rustup override set nightly
info: using existing install for 'nightly-x86_64-unknown-linux-gnu'
info: override toolchain for '/home/yury/PROJECTS/TESTS/RUST/clippy_test' set to 'nightly-x86_64-unknown-linux-gnu'
nightly-x86_64-unknown-linux-gnu unchanged - rustc 1.57.0-nightly (e30b68353 2021-09-05)
After doing this if you list the toolchains for this project, you should get the following output:
$ rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu (override)
Now, you can use the --fix
clippy feature. I use additional --allow-no-vcs
flag because this test project I have created without version control system support: cargo new clippy_test --vcs none
. So as during the fixing clippy rewrites the code, its usage is considered as insecure, therefore, you need an additional flag to make clippy working.
$ cargo clippy --fix --allow-no-vcs
Checking clippy_test v0.1.0 (/home/yury/PROJECTS/TESTS/RUST/clippy_test)
Fixed src/main.rs (2 fixes)
warning: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:4:21
|
4 | const PI: f32 = 3.14;
| ^^^^
|
= note: `#[warn(clippy::approx_constant)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
warning: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:12:21
|
12 | const PI: f32 = 3.14;
| ^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
warning: `clippy_test` (bin "clippy_test") generated 2 warnings
warning: `clippy_test` (bin "clippy_test" test) generated 2 warnings (2 duplicates)
Finished dev [unoptimized + debuginfo] target(s) in 0.14s
You can see that clippy automatically fixed 2 issues:
return false;
- thereturn
is redundant;if equal == true {
- there is not need to compare boolean values.
Note that clippy cannot correct all the issues automatically, some of them you have to correct manually:
$ cargo clippy
Checking clippy_test v0.1.0 (/home/yury/PROJECTS/TESTS/RUST/clippy_test)
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:4:21
|
4 | const PI: f32 = 3.14;
| ^^^^
|
= note: `#[deny(clippy::approx_constant)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:12:21
|
12 | const PI: f32 = 3.14;
| ^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
error: could not compile `clippy_test` due to 2 previous errors
After you have made the modifications, you can change the toolchain back to stable (if you develop using the stable toolchain) by executing the following command in the project’s directory:
$ rustup override unset
info: override toolchain for '/home/yury/PROJECTS/TESTS/RUST/clippy_test' removed
yury@ideap:~/PROJECTS/TESTS/RUST/clippy_test$ rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu
Alternatively, you can once nightly toolchain is installed in your system, you can directly call clippy using special command line options. For instance, if you do not want to change the default toolchain or do not want to override it for particular project, you can remember the following command:
$ cargo +nightly clippy --fix -Z unstable-options --allow-no-vcs
Additional Cargo Clippy CLI Options
Sometimes in order to change what clippy checks, you need to provide additional command line options. For instance, if clippy is run as a task in your CI pipeline you may want to check only the given crate without the dependencies, or you would also like to lint the tests or other conditionally compiled targets.
For instance, let’s augment our example with the following test function:
#[test]
fn test_num_equal_to_approx_pi() {
const PI: f32 = 3.14;
let equal = 3.14;
assert_eq!(equal, PI);
}
If you run clippy on this code, it will not report errors in this test function:
Clippy Output
$ cargo clippy
Checking clippy_test v0.1.0 (/home/yury/PROJECTS/TESTS/RUST/clippy_test)
warning: unneeded `return` statement
--> src/main.rs:8:5
|
8 | return false;
| ^^^^^^^^^^^^^ help: remove `return`: `false`
|
= note: `#[warn(clippy::needless_return)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:4:21
|
4 | const PI: f32 = 3.14;
| ^^^^
|
= note: `#[deny(clippy::approx_constant)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:12:21
|
12 | const PI: f32 = 3.14;
| ^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
warning: equality checks against true are unnecessary
--> src/main.rs:19:8
|
19 | if equal == true {
| ^^^^^^^^^^^^^ help: try simplifying it as shown: `equal`
|
= note: `#[warn(clippy::bool_comparison)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
warning: `clippy_test` (bin "clippy_test") generated 2 warnings
error: could not compile `clippy_test` due to 2 previous errors; 2 warnings emitted
As you can see, the output contains no information about the problems in the test function. In order to make clippy to check tests, run it with the --tests
command line flag:
Clippy Output
$ cargo clippy --tests
Checking clippy_test v0.1.0 (/home/yury/PROJECTS/TESTS/RUST/clippy_test)
warning: unneeded `return` statement
--> src/main.rs:8:5
|
8 | return false;
| ^^^^^^^^^^^^^ help: remove `return`: `false`
|
= note: `#[warn(clippy::needless_return)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:4:21
|
4 | const PI: f32 = 3.14;
| ^^^^
|
= note: `#[deny(clippy::approx_constant)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:12:21
|
12 | const PI: f32 = 3.14;
| ^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
warning: equality checks against true are unnecessary
--> src/main.rs:19:8
|
19 | if equal == true {
| ^^^^^^^^^^^^^ help: try simplifying it as shown: `equal`
|
= note: `#[warn(clippy::bool_comparison)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:28:21
|
28 | const PI: f32 = 3.14;
| ^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly
--> src/main.rs:29:17
|
29 | let equal = 3.14;
| ^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
error: strict comparison of `f32` or `f64`
--> src/main.rs:30:5
|
30 | assert_eq!(equal, PI);
| ^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[deny(clippy::float_cmp)]` on by default
= note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
warning: `clippy_test` (bin "clippy_test" test) generated 2 warnings
error: could not compile `clippy_test` due to 5 previous errors; 2 warnings emitted
Similarly, clippy will check only default features and default targets. To check all targets and all features run clippy with the --all-targets
and --all-features
flags correspondingly.
The list of available options is the same as for cargo check
command. You can see them in the help:
$ cargo check --help
cargo-check
Check a local package and all of its dependencies for errors
USAGE:
cargo check [OPTIONS]
OPTIONS:
-q, --quiet No output printed to stdout
-p, --package <SPEC>... Package(s) to check
--workspace Check all packages in the workspace
--exclude <SPEC>... Exclude packages from the check
--all Alias for --workspace (deprecated)
-j, --jobs <N> Number of parallel jobs, defaults to # of CPUs
--lib Check only this package's library
--bin <NAME>... Check only the specified binary
--bins Check all binaries
--example <NAME>... Check only the specified example
--examples Check all examples
--test <NAME>... Check only the specified test target
--tests Check all tests
--bench <NAME>... Check only the specified bench target
--benches Check all benches
--all-targets Check all targets
--release Check artifacts in release mode, with optimizations
--profile <PROFILE-NAME> Check artifacts with the specified profile
--features <FEATURES>... Space or comma separated list of features to activate
--all-features Activate all available features
--no-default-features Do not activate the `default` feature
--target <TRIPLE>... Check for the target triple
--target-dir <DIRECTORY> Directory for all generated artifacts
--manifest-path <PATH> Path to Cargo.toml
--ignore-rust-version Ignore `rust-version` specification in packages
--message-format <FMT>... Error format
--unit-graph Output build graph in JSON (unstable)
--future-incompat-report Outputs a future incompatibility report at the end of the build (unstable)
-v, --verbose Use verbose output (-vv very verbose/build.rs output)
--color <WHEN> Coloring: auto, always, never
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
--offline Run without accessing the network
--config <KEY=VALUE>... Override a configuration value (unstable)
-Z <FLAG>... Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
-h, --help Prints help information
Run `cargo help check` for more detailed information.
The only difference is the --no-deps
option that makes clippy to check only current crate without the dependencies.