Checks for usage of items through absolute paths, likestd::env::current_dir.
Many codebases have their own style when it comes to importing, but one that is seldom usedis using absolute pathseverywhere. This is generally considered unidiomatic, and youshould add ause statement.
The default maximum segments (2) is pretty strict, you may want to increase this inclippy.toml.
Note: One exception to this is code from macro expansion - this does not lint such cases, asusing absolute paths is the proper way of referencing items in one.
There are currently a few cases which are not caught by this lint:
path::to::macro!()#[derive(path::to::macro)]#[path::to::macro]let x = std::f64::consts::PI;Use any of the below instead, or anything else:
use std::f64;use std::f64::consts;use std::f64::consts::PI;let x = f64::consts::PI;let x = consts::PI;let x = PI;use std::f64::consts as f64_consts;let x = f64_consts::PI;absolute-paths-allowed-crates: Which crates to allow absolute paths from
(default:[])
absolute-paths-max-segments: The maximum number of segments a path can have before being linted, anything above this willbe linted.
(default:2)
Checks for comparisons where one side of the relation iseither the minimum or maximum value for its type and warns if it involves acase that is always true or always false. Only integer and boolean types arechecked.
An expression likemin <= x may misleadingly implythat it is possible forx to be less than the minimum. Expressions likemax < x are probably mistakes.
Forusize the size of the current compile target willbe assumed (e.g., 64 bits on 64 bit systems). This means code that uses sucha comparison to detect target pointer width will trigger this lint. One canusemem::sizeof and compare its value or conditional compilationattributeslike#[cfg(target_pointer_width = "64")] .. instead.
let vec: Vec<isize> = Vec::new();if vec.len() <= 0 {}if 100 > i32::MAX {}Finds items imported throughalloc when available throughcore.
Crates which haveno_std compatibility and may optionally require alloc may wish to ensure types areimported from core to ensure disablingalloc does not cause the crate to fail to compile. This lintis also useful for crates migrating to becomeno_std compatible.
The lint is only partially aware of the required MSRV for items that were originally instd but movedtocore.
use alloc::slice::from_ref;Use instead:
use core::slice::from_ref;Checks for usage of the#[allow] attribute and suggests replacing it withthe#[expect] attribute (SeeRFC 2383)
This lint only warns outer attributes (#[allow]), as inner attributes(#![allow]) are usually used to enable or disable lints on a global scale.
#[expect] attributes suppress the lint emission, but emit a warning, ifthe expectation is unfulfilled. This can be useful to be notified when thelint is no longer triggered.
#[allow(unused_mut)]fn foo() -> usize { let mut a = Vec::new(); a.len()}Use instead:
#[expect(unused_mut)]fn foo() -> usize { let mut a = Vec::new(); a.len()}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for attributes that allow lints without a reason.
Justifying eachallow helps readers understand the reasoning,and may allow removingallow attributes if their purpose is obsolete.
#![allow(clippy::some_lint)]Use instead:
#![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")]msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for ranges which almost include the entire range of letters from ‘a’ to ‘z’or digits from ‘0’ to ‘9’, but don’t because they’re a half open range.
This ('a'..'z') is almost certainly a typo meant to include all letters.
let _ = 'a'..'z';Use instead:
let _ = 'a'..='z';msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks forfoo = bar; bar = foo sequences.
This looks like a failed attempt to swap.
a = b;b = a;If swapping is intended, useswap() instead:
std::mem::swap(&mut a, &mut b);Checks for floating point literals that approximateconstants which are defined instd::f32::constsorstd::f64::consts,respectively, suggesting to use the predefined constant.
Usually, the definition in the standard library is moreprecise than what people come up with. If you find that your definition isactually more precise, pleasefile a Rustissue.
let x = 3.14;let y = 1_f64 / x;Use instead:
let x = std::f32::consts::PI;let y = std::f64::consts::FRAC_1_PI;msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Confirms that items are sorted in source files as per configuration.
Keeping a consistent ordering throughout the codebase helps with workingas a team, and possibly improves maintainability of the codebase. Theidea is that by defining a consistent and enforceable rule for howsource files are structured, less time will be wasted during reviews ona topic that is (under most circumstances) not relevant to the logicimplemented in the code. Sometimes this will be referred to as“bikeshedding”.
The content of items with a representation clause attribute, such as#[repr(C)] will not be checked, as the order of their fields orvariants might be dictated by an external API (application binaryinterface).
As there is no generally applicable rule, and each project may havedifferent requirements, the lint can be configured with highgranularity. The configuration is split into two stages:
The item kinds that can be linted are:
Due to the large variation of items within modules, the ordering can beconfigured on a very granular level. Item kinds can be grouped togetherarbitrarily, items within groups will be ordered alphabetically. Thefollowing table shows the default groupings:
| Group | Item Kinds |
|---|---|
modules | “mod”, “foreign_mod” |
use | “use” |
macros | “macro” |
global_asm | “global_asm” |
UPPER_SNAKE_CASE | “static”, “const” |
PascalCase | “ty_alias”, “opaque_ty”, “enum”, “struct”, “union”, “trait”, “trait_alias”, “impl” |
lower_snake_case | “fn” |
The groups’ names are arbitrary and can be changed to suit theconventions that should be enforced for a specific project.
All item kinds must be accounted for to create an enforceable lintingrule set. Following are some example configurations that may be useful.
Example:module inclusions and use statements to be at the top
module-item-order-groupings = [ [ "modules", [ "extern_crate", "mod", "foreign_mod" ], ], [ "use", [ "use", ], ], [ "everything_else", [ "macro", "global_asm", "static", "const", "ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl", "fn", ], ],]Example:only consts and statics should be alphabetically ordered
It is also possible to configure a selection of module item groups thatshould be ordered alphabetically. This may be useful if for examplestatics and consts should be ordered, but the rest should be left open.
module-items-ordered-within-groupings = ["UPPER_SNAKE_CASE"]Keep in mind, that ordering source code alphabetically can lead toreduced performance in cases where the most commonly used enum variantisn’t the first entry anymore, and similar optimizations that can reducebranch misses, cache locality and such. Either don’t use this lint ifthat’s relevant, or disable the lint in modules or items specificallywhere it matters. Other solutions can be to use profile guidedoptimization (PGO), post-link optimization (e.g. using BOLT for LLVM),or other advanced optimization methods. A good starting point to diginto optimization iscargo-pgo.
The lint can be disabled only on a “contains” basis, but not per elementwithin a “container”, e.g. the lint works per-module, per-struct,per-enum, etc. but not for “don’t order this particular enum variant”.
Module level rustdoc comments are not part of the resulting syntax treeand as such cannot be linted from withincheck_mod. Instead, therustdoc::missing_documentation lint may be used.
This lint does not implement detection of module tests (or other featuredependent elements for that matter). To lint the location of mod tests,the lintitems_after_test_module can be used instead.
trait TraitUnordered { const A: bool; const C: bool; const B: bool; type SomeType; fn a(); fn c(); fn b();}Use instead:
trait TraitOrdered { const A: bool; const B: bool; const C: bool; type SomeType; fn a(); fn b(); fn c();}module-item-order-groupings: The named groupings of different source item kinds within modules.
(default:[["modules", ["extern_crate", "mod", "foreign_mod"]], ["use", ["use"]], ["macros", ["macro"]], ["global_asm", ["global_asm"]], ["UPPER_SNAKE_CASE", ["static", "const"]], ["PascalCase", ["ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl"]], ["lower_snake_case", ["fn"]]])
module-items-ordered-within-groupings: Whether the items within module groups should be ordered alphabetically or not.
This option can be configured to “all”, “none”, or a list of specific grouping names that should be checked(e.g. only “enums”).
(default:"none")
source-item-ordering: Which kind of elements should be ordered internally, possible values beingenum,impl,module,struct,trait.
(default:["enum", "impl", "module", "struct", "trait"])
trait-assoc-item-kinds-order: The order of associated items in traits.
(default:["const", "type", "fn"])
This lint warns when you useArc with a type that does not implementSend orSync.
Arc<T> is a thread-safeRc<T> and guarantees that updates to the reference counteruse atomic operations. To send anArc<T> across thread boundaries andshare ownership between multiple threads,T must bebothSend andSync,so eitherT should be madeSend + Sync or anRc should be used instead of anArc.
fn main() { // This is fine, as `i32` implements `Send` and `Sync`. let a = Arc::new(42); // `RefCell` is `!Sync`, so either the `Arc` should be replaced with an `Rc` // or the `RefCell` replaced with something like a `RwLock` let b = Arc::new(RefCell::new(42));}Checks any kind of arithmetic operation of any type.
Operators like+,-,* or<< are usually capable of overflowing according to theRustReference,or can panic (/,%).
Known safe built-in types likeWrapping orSaturating, floats, operations in constantenvironments, allowed types and non-constant operations that won’t overflow are ignored.
For integers, overflow will trigger a panic in debug builds or wrap the result inrelease mode; division by zero will cause a panic in either mode. As a result, it isdesirable to explicitly call checked, wrapping or saturating arithmetic methods.
// `n` can be any number, including `i32::MAX`.fn foo(n: i32) -> i32 { n + 1}Third-party types can also overflow or present unwanted side-effects.
use rust_decimal::Decimal;let _n = Decimal::MAX + Decimal::MAX;arithmetic-side-effects-allowed: Suppress checking of the passed type names in all types of operations.If a specific operation is desired, consider usingarithmetic_side_effects_allowed_binary orarithmetic_side_effects_allowed_unary instead.
arithmetic-side-effects-allowed = ["SomeType", "AnotherType"]A type, saySomeType, listed in this configuration has the same behavior of["SomeType" , "*"], ["*", "SomeType"] inarithmetic_side_effects_allowed_binary.
(default:[])
arithmetic-side-effects-allowed-binary: Suppress checking of the passed type pair names in binary operations like addition ormultiplication.Supports the “*” wildcard to indicate that a certain type won’t trigger the lint regardlessof the involved counterpart. For example,["SomeType", "*"] or["*", "AnotherType"].
Pairs are asymmetric, which means that["SomeType", "AnotherType"] is not the same as["AnotherType", "SomeType"].
arithmetic-side-effects-allowed-binary = [["SomeType" , "f32"], ["AnotherType", "*"]](default:[])
arithmetic-side-effects-allowed-unary: Suppress checking of the passed type names in unary operations like “negation” (-).arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"](default:[])
Checks for usage ofas conversions.
Note that this lint is specialized in lintingevery single use ofasregardless of whether good alternatives exist or not. If you want moreprecise lints foras, please consider using these separate lints:
clippy::cast_losslessclippy::cast_possible_truncationclippy::cast_possible_wrapclippy::cast_precision_lossclippy::cast_sign_lossclippy::char_lit_as_u8clippy::fn_to_numeric_castclippy::fn_to_numeric_cast_with_truncationclippy::ptr_as_ptrclippy::unnecessary_castinvalid_reference_castingThere is a good explanation the reason why this lint should work in thisway and how it is usefulin thisissue.
as conversions will perform many kinds ofconversions, including silently lossy conversions and dangerous coercions.There are cases when it makes sense to useas, so the lint isAllow by default.
let a: u32;...f(a as u16);Use instead:
f(a.try_into()?);// orf(a.try_into().expect("Unexpected u16 overflow in f"));Checks for the usage ofas *const _ oras *mut _ conversion using inferred type.
The conversion might include a dangerous cast that might go undetected due to the type being inferred.
fn as_usize<T>(t: &T) -> usize { // BUG: `t` is already a reference, so we will here // return a dangling pointer to a temporary value instead &t as *const _ as usize}Use instead:
fn as_usize<T>(t: &T) -> usize { t as *const T as usize}Checks for the result of a&self-takingas_ptr being cast to a mutable pointer.
Sinceas_ptr takes a&self, the pointer won’t have write permissions unless interiormutability is used, making it unlikely that having it as a mutable pointer is correct.
let mut vec = Vec::<u8>::with_capacity(1);let ptr = vec.as_ptr() as *mut u8;unsafe { ptr.write(4) }; // UNDEFINED BEHAVIOURUse instead:
let mut vec = Vec::<u8>::with_capacity(1);let ptr = vec.as_mut_ptr();unsafe { ptr.write(4) };Checks for the usage ofas _ conversion using inferred type.
The conversion might include lossy conversion or a dangerous cast that might goundetected due to the type being inferred.
The lint is allowed by default as using_ is less wordy than always specifying the type.
fn foo(n: usize) {}let n: u16 = 256;foo(n as _);Use instead:
fn foo(n: usize) {}let n: u16 = 256;foo(n as usize);Checks forassert!(true) andassert!(false) calls.
Will be optimized out by the compiler or should probably be replaced by apanic!() orunreachable!()
assert!(false)assert!(true)const B: bool = false;assert!(B)Checks forassert!(r.is_ok()) orassert!(r.is_err()) calls.
This form of assertion does not show any of the information present in theResultother than which variant it isn’t.
The suggested replacement decreases the readability of code and log output.
assert!(r.is_ok());assert!(r.is_err());Use instead:
r.unwrap();r.unwrap_err();Checks fora = a op b ora = b commutative_op apatterns.
These can be written as the shortera op= b.
While forbidden by the spec,OpAssign traits may haveimplementations that differ from the regularOp impl.
let mut a = 5;let b = 0;// ...a = a + b;Use instead:
let mut a = 5;let b = 0;// ...a += b;Nothing. This lint has been deprecated
Compound operators are harmless and linting on them is not in scope for clippy.
Checks for code likefoo = bar.clone();
CustomClone::clone_from() orToOwned::clone_into implementations allow the objectsto share resources and therefore avoid allocations.
struct Thing;impl Clone for Thing { fn clone(&self) -> Self { todo!() } fn clone_from(&mut self, other: &Self) { todo!() }}pub fn assign_to_ref(a: &mut Thing, b: Thing) { *a = b.clone();}Use instead:
struct Thing;impl Clone for Thing { fn clone(&self) -> Self { todo!() } fn clone_from(&mut self, other: &Self) { todo!() }}pub fn assign_to_ref(a: &mut Thing, b: Thing) { a.clone_from(&b);}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for async blocks that yield values of typesthat can themselves be awaited.
An await is likely missing.
async fn foo() {}fn bar() { let x = async { foo() };}Use instead:
async fn foo() {}fn bar() { let x = async { foo().await };}Allows users to configure types which should not be held across awaitsuspension points.
There are some types which are perfectly safe to use concurrently froma memory access perspective, but that will cause bugs at runtime ifthey are held in such a way.
await-holding-invalid-types = [ # You can specify a type name "CustomLockType", # You can (optionally) specify a reason { path = "OtherCustomLockType", reason = "Relies on a thread local" }]struct CustomLockType;struct OtherCustomLockType;async fn foo() { let _x = CustomLockType; let _y = OtherCustomLockType; baz().await; // Lint violation}await-holding-invalid-types: The list of types which may not be held across an await point.
(default:[])
Checks for calls toawait while holding a non-async-awareMutexGuard.
The Mutex types found instd::sync andparking_lot arenot designed to operate in an async context across await points.
There are two potential solutions. One is to use an async-awareMutextype. Many asynchronous foundation crates provide such aMutex type.The other solution is to ensure the mutex is unlocked before callingawait, either by introducing a scope or an explicit call toDrop::drop.
Will report false positive for explicitly dropped guards(#6446). Aworkaround for this is to wrap the.lock() call in a block instead ofexplicitly dropping the guard.
async fn foo(x: &Mutex<u32>) { let mut guard = x.lock().unwrap(); *guard += 1; baz().await;}async fn bar(x: &Mutex<u32>) { let mut guard = x.lock().unwrap(); *guard += 1; drop(guard); // explicit drop baz().await;}Use instead:
async fn foo(x: &Mutex<u32>) { { let mut guard = x.lock().unwrap(); *guard += 1; } baz().await;}async fn bar(x: &Mutex<u32>) { { let mut guard = x.lock().unwrap(); *guard += 1; } // guard dropped here at end of scope baz().await;}Checks for calls toawait while holding aRefCell,Ref, orRefMut.
RefCell refs only check for exclusive mutable accessat runtime. Holding aRefCell ref across an await suspension pointrisks panics from a mutable ref shared while other refs are outstanding.
Will report false positive for explicitly dropped refs(#6353). A workaround for this isto wrap the.borrow[_mut]() call in a block instead of explicitly dropping the ref.
async fn foo(x: &RefCell<u32>) { let mut y = x.borrow_mut(); *y += 1; baz().await;}async fn bar(x: &RefCell<u32>) { let mut y = x.borrow_mut(); *y += 1; drop(y); // explicit drop baz().await;}Use instead:
async fn foo(x: &RefCell<u32>) { { let mut y = x.borrow_mut(); *y += 1; } baz().await;}async fn bar(x: &RefCell<u32>) { { let mut y = x.borrow_mut(); *y += 1; } // y dropped here at end of scope baz().await;}Checks for incompatible bit masks in comparisons.
The formula for detecting if an expression of the type_ <bit_op> m <cmp_op> c (where<bit_op> is one of {&,|} and<cmp_op> is one of{!=,>=,>,!=,>=,>}) can be determined from the followingtable:
| Comparison | Bit Op | Example | is always | Formula |
|---|---|---|---|---|
== or!= | & | x & 2 == 3 | false | c & m != c |
< or>= | & | x & 2 < 3 | true | m < c |
> or<= | & | x & 1 > 1 | false | m <= c |
== or!= | | | x | 1 == 0 | false | c | m != c |
< or>= | | | x | 1 < 1 | false | m >= c |
<= or> | | | x | 1 > 0 | true | m > c |
If the bits that the comparison cares about are alwaysset to zero or one by the bit mask, the comparison is constanttrue orfalse (depending on mask, compared value, and operators).
So the code is actively misleading, and the only reason someone would writethis intentionally is to win an underhanded Rust contest or create atest-case for this lint.
if (x & 1 == 2) { }Checks for the usage of theto_be_bytes method and/or the functionfrom_be_bytes.
To ensure use of little-endian or the target’s endianness rather than big-endian.
let _x = 2i32.to_be_bytes();let _y = 2i64.to_be_bytes();Checks for usage of_.and_then(|x| Some(y)),_.and_then(|x| Ok(y))or_.or_else(|x| Err(y)).
This can be written more concisely as_.map(|x| y) or_.map_err(|x| y).
let _ = opt().and_then(|s| Some(s.len()));let _ = res().and_then(|s| if s.len() == 42 { Ok(10) } else { Ok(20) });let _ = res().or_else(|s| if s.len() == 42 { Err(10) } else { Err(20) });The correct use would be:
let _ = opt().map(|s| s.len());let _ = res().map(|s| if s.len() == 42 { 10 } else { 20 });let _ = res().map_err(|s| if s.len() == 42 { 10 } else { 20 });Checks forwarn/deny/forbid attributes targeting the whole clippy::restriction category.
Restriction lints sometimes are in contrast with other lints or even go against idiomatic rust.These lints should only be enabled on a lint-by-lint basis and with careful consideration.
#![deny(clippy::restriction)]Use instead:
#![deny(clippy::as_conversions)]Checks forif andmatch conditions that use blocks containing anexpression, statements or conditions that use closures with blocks.
Style, using blocks in the condition makes it hard to read.
if { true } { /* ... */ }if { let x = somefunc(); x } { /* ... */ }match { let e = somefunc(); e } { // ...}Use instead:
if true { /* ... */ }let res = { let x = somefunc(); x };if res { /* ... */ }let res = { let e = somefunc(); e };match res { // ...}This lint warns about boolean comparisons in assert-like macros.
It is shorter to use the equivalent.
assert_eq!("a".is_empty(), false);assert_ne!("a".is_empty(), true);Use instead:
assert!(!"a".is_empty());Checks for expressions of the formx == true,x != true and order comparisons such asx < true (or vice versa) andsuggest using the variable directly.
Unnecessary code.
if x == true {}if y == false {}usex directly:
if x {}if !y {}Instead of using an if statement to convert a bool to an int,this lint suggests using afrom() function or anas coercion.
Coercion orfrom() is another way to convert bool to a number.Both methods are guaranteed to return 1 for true, and 0 for false.
See https://doc.rust-lang.org/std/primitive.bool.html#impl-From%3Cbool%3E
if condition { 1_i64} else { 0};Use instead:
i64::from(condition);or
condition as i64;Checks for the usage of&expr as *const T or&mut expr as *mut T, and suggest using&raw const or&raw mut instead.
This would improve readability and avoid creating a referencethat points to an uninitialized value or unaligned place.Read the&raw explanation in the Reference for more information.
let val = 1;let p = &val as *const i32;let mut val_mut = 1;let p_mut = &mut val_mut as *mut i32;Use instead:
let val = 1;let p = &raw const val;let mut val_mut = 1;let p_mut = &raw mut val_mut;msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for&*(&T).
Dereferencing and then borrowing a reference value has no effect in most cases.
False negative on such code:
let x = &12;let addr_x = &x as *const _ as usize;let addr_y = &&*x as *const _ as usize; // assert ok now, and lint triggered. // But if we fix it, assert will fail.assert_ne!(addr_x, addr_y);let s = &String::new();let a: &String = &* s;Use instead:
let a: &String = s;Checks for a borrow of a named constant with interior mutability.
Named constants are copied at every use site which means any change to their valuewill be lost after the newly created value is dropped. e.g.
use core::sync::atomic::{AtomicUsize, Ordering};const ATOMIC: AtomicUsize = AtomicUsize::new(0);fn add_one() -> usize { // This will always return `0` since `ATOMIC` is copied before it's borrowed // for use by `fetch_add`. ATOMIC.fetch_add(1, Ordering::AcqRel)}This lint does not, and cannot in general, determine if the borrow of the constantis used in a way which causes a mutation. e.g.
use core::cell::Cell;const CELL: Cell<usize> = Cell::new(0);fn get_cell() -> Cell<usize> { // This is fine. It borrows a copy of `CELL`, but never mutates it through the // borrow. CELL.clone()}There also exists types which contain private fields with interior mutability, butno way to both create a value as a constant and modify any mutable field using thetype’s public interface (e.g.bytes::Bytes). As there is no reasonable way toscan a crate’s interface to see if this is the case, all such types will be linted.If this happens use theignore-interior-mutability configuration option to allowthe type.
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchangedassert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinctUse instead:
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);static STATIC_ATOM: AtomicUsize = CONST_ATOM;STATIC_ATOM.store(9, SeqCst);assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instanceignore-interior-mutability: A list of paths to types that should be treated as if they do not contain interior mutability
(default:["bytes::Bytes"])
Checks for usage of&Box<T> anywhere in the code.Check theBox documentation for more information.
A&Box<T> parameter requires the function caller to boxT first before passing it to a function.Using&T defines a concrete type for the parameter and generalizes the function, this would alsoauto-deref to&T at the function call site if passed a&Box<T>.
fn foo(bar: &Box<T>) { ... }Better:
fn foo(bar: &T) { ... }Checks for usage ofBox<T> where T is a collection such as Vec anywhere in the code.Check theBox documentation for more information.
Collections already keeps their contents in a separate area onthe heap. So if youBox them, you just add another level of indirectionwithout any benefit whatsoever.
struct X { values: Box<Vec<Foo>>,}Better:
struct X { values: Vec<Foo>,}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
checks forBox::new(Default::default()), which can be written asBox::default().
Box::default() is equivalent and more concise.
let x: Box<String> = Box::new(Default::default());Use instead:
let x: Box<String> = Box::default();Checks for usage ofBox<T> where an unboxedT wouldwork fine.
This is an unnecessary allocation, and bad forperformance. It is only necessary to allocate if you wish to move the boxinto something.
fn foo(x: Box<u32>) {}Use instead:
fn foo(x: u32) {}too-large-for-stack: The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
(default:200)
Checks if theif andelse block contain shared code that can bemoved out of the blocks.
Duplicate code is less maintainable.
let foo = if … { println!("Hello World"); 13} else { println!("Hello World"); 42};Use instead:
println!("Hello World");let foo = if … { 13} else { 42};Warns if a generic shadows a built-in type.
This gives surprising type errors.
impl<u32> Foo<u32> { fn impl_func(&self) -> u32 { 42 }}Checks for hard to read slices of byte characters, that could be more easily expressed as abyte string.
Potentially makes the string harder to read.
&[b'H', b'e', b'l', b'l', b'o'];Use instead:
b"Hello"It checks forstr::bytes().count() and suggests replacing it withstr::len().
str::bytes().count() is longer and may not be as performant as usingstr::len().
"hello".bytes().count();String::from("hello").bytes().count();Use instead:
"hello".len();String::from("hello").len();Checks for the use of.bytes().nth().
.as_bytes().get() is more efficient and morereadable.
"Hello".bytes().nth(3);Use instead:
"Hello".as_bytes().get(3);Checks to see if all common metadata is defined inCargo.toml. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata
It will be more difficult for users to discover thepurpose of the crate, and key information related to it.
[package]name = "clippy"version = "0.0.212"repository = "https://github.com/rust-lang/rust-clippy"readme = "README.md"license = "MIT OR Apache-2.0"keywords = ["clippy", "lint", "plugin"]categories = ["development-tools", "development-tools::cargo-plugins"]Should include a description field like:
[package]name = "clippy"version = "0.0.212"description = "A bunch of helpful lints to avoid common pitfalls in Rust"repository = "https://github.com/rust-lang/rust-clippy"readme = "README.md"license = "MIT OR Apache-2.0"keywords = ["clippy", "lint", "plugin"]categories = ["development-tools", "development-tools::cargo-plugins"]cargo-ignore-publish: For internal testing only, ignores the currentpublish settings in the Cargo manifest.
(default:false)
Checks for calls toends_with with possible file extensionsand suggests to use a case-insensitive approach instead.
ends_with is case-sensitive and may not detect files with a valid extension.
fn is_rust_file(filename: &str) -> bool { filename.ends_with(".rs")}Use instead:
fn is_rust_file(filename: &str) -> bool { let filename = std::path::Path::new(filename); filename.extension() .map_or(false, |ext| ext.eq_ignore_ascii_case("rs"))}Checks for usage of theabs() method that cast the result to unsigned.
Theunsigned_abs() method avoids panic when called on the MIN value.
let x: i32 = -42;let y: u32 = x.abs() as u32;Use instead:
let x: i32 = -42;let y: u32 = x.unsigned_abs();msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for casts from an enum tuple constructor to an integer.
The cast is easily confused with casting a c-like enum value to an integer.
enum E { X(i32) };let _ = E::X as usize;Checks for casts from an enum type to an integral type that will definitely truncate thevalue.
The resulting integral value will not match the value of the variant it came from.
enum E { X = 256 };let _ = E::X as u8;Checks for casts between numeric types that can be replaced by safeconversion functions.
Rust’sas keyword will perform many kinds of conversions, includingsilently lossy conversions. Conversion functions such asi32::fromwill only perform lossless conversions. Using the conversion functionsprevents conversions from becoming silently lossy if the input typesever change, and makes it clear for people reading the code that theconversion is lossless.
fn as_u64(x: u8) -> u64 { x as u64}Using::from would look like this:
fn as_u64(x: u8) -> u64 { u64::from(x)}Checks for a known NaN float being cast to an integer
NaNs are cast into zero, so one could simply use this and make thecode more readable. The lint could also hint at a programmer error.
let _ = (0.0_f32 / 0.0) as u64;Use instead:
let _ = 0_u64;Checks for casts between numeric types that maytruncate large values. This is expected behavior, so the cast isAllow bydefault. It suggests user either explicitly ignore the lint,or usetry_from() and handle the truncation, default, or panic explicitly.
In some problem domains, it is good practice to avoidtruncation. This lint can be activated to help assess where additionalchecks could be beneficial.
fn as_u8(x: u64) -> u8 { x as u8}Use instead:
fn as_u8(x: u64) -> u8 { if let Ok(x) = u8::try_from(x) { x } else { todo!(); }}// Or#[allow(clippy::cast_possible_truncation)]fn as_u16(x: u64) -> u16 { x as u16}Checks for casts from an unsigned type to a signed type ofthe same size, or possibly smaller due to target-dependent integers.Performing such a cast is a no-op for the compiler (that is, nothing ischanged at the bit level), and the binary representation of the value isreinterpreted. This can cause wrapping if the value is too bigfor the target signed type. However, the cast works as defined, so this lintisAllow by default.
While such a cast is not bad in itself, the results canbe surprising when this is not the intended behavior:
let _ = u32::MAX as i32; // will yield a value of `-1`Use instead:
let _ = i32::try_from(u32::MAX).ok();Checks for casts from any numeric type to a float type wherethe receiving type cannot store all values from the original type withoutrounding errors. This possible rounding is to be expected, so this lint isAllow by default.
Basically, this warns on casting any integer with 32 or more bits tof32or any 64-bit integer tof64.
It’s not bad at all. But in some applications it can behelpful to know where precision loss can take place. This lint can help findthose places in the code.
let x = u64::MAX;x as f64;Checks for casts, usingas orpointer::cast, from aless strictly aligned pointer to a more strictly aligned pointer.
Dereferencing the resulting pointer may be undefined behavior.
Usingstd::ptr::read_unaligned andstd::ptr::write_unaligned orsimilar on the resulting pointer is fine. Is over-zealous: casts withmanual alignment checks or casts likeu64 ->u8 ->u16 can befine. Miri is able to do a more in-depth analysis.
let _ = (&1u8 as *const u8) as *const u16;let _ = (&mut 1u8 as *mut u8) as *mut u16;(&1u8 as *const u8).cast::<u16>();(&mut 1u8 as *mut u8).cast::<u16>();Checks for casts from a signed to an unsigned numerictype. In this case, negative values wrap around to large positive values,which can be quite surprising in practice. However, since the cast works asdefined, this lint isAllow by default.
Possibly surprising results. You can activate this lintas a one-time check to see where numeric wrapping can arise.
let y: i8 = -1;y as u64; // will return 18446744073709551615Checks foras casts between raw pointers to slices with differently sized elements.
The produced raw pointer to a slice does not update its length metadata. The producedpointer will point to a different number of bytes than the original pointer because thelength metadata of a raw slice pointer is in elements rather than bytes.Producing a slice reference from the raw pointer will either create a slice withless data (which can be surprising) or create a slice with more data and cause Undefined Behavior.
// Missing data
let a = [1_i32, 2, 3, 4];let p = &a as *const [i32] as *const [u8];unsafe { println!("{:?}", &*p);}// Undefined Behavior (note: also potential alignment issues)
let a = [1_u8, 2, 3, 4];let p = &a as *const [u8] as *const [u32];unsafe { println!("{:?}", &*p);}Instead useptr::slice_from_raw_parts to construct a slice from a data pointer and the correct length
let a = [1_i32, 2, 3, 4];let old_ptr = &a as *const [i32];// The data pointer is cast to a pointer to the target `u8` not `[u8]`// The length comes from the known length of 4 i32s times the 4 bytes per i32let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16);unsafe { println!("{:?}", &*new_ptr);}Checks for a raw slice being cast to a slice pointer
This can result in multiple&mut references to the same location when only a pointer isrequired.ptr::slice_from_raw_parts is a safe alternative that doesn’t requirethe samesafety requirements to be upheld.
let _: *const [u8] = std::slice::from_raw_parts(ptr, len) as *const _;let _: *mut [u8] = std::slice::from_raw_parts_mut(ptr, len) as *mut _;Use instead:
let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, len);let _: *mut [u8] = std::ptr::slice_from_raw_parts_mut(ptr, len);Checks for usage ofcfg that excludes code fromtest builds. (i.e.,#[cfg(not(test))])
This may give the false impression that a codebase has 100% coverage, yet actually has untested code.Enabling this also guards against excessive mockery as well, which is an anti-pattern.
#[cfg(not(test))]important_check(); // I'm not actually tested, but not including me will falsely increase coverage!Use instead:
important_check();Checks for usage of a character position yielded by.chars().enumerate() in a context where abyte index is expected,such as an argument to a specificstr method or indexing into astr orString.
A character (more specifically, a Unicode scalar value) that is yielded bystr::chars can take up multiple bytes,so a character position does not necessarily have the same byte index at which the character is stored.Thus, using the character position where a byte index is expected can unexpectedly return wrong valuesor panic when the string consists of multibyte characters.
For example, the charactera inäa is stored at byte index 2 but has the character position 1.Using the character position 1 to index into the string will lead to a panic as it is in the middle of the first character.
Instead of.chars().enumerate(), the correct iterator to use is.char_indices(), which yields byte indices.
This pattern is technically fine if the strings are known to only use the ASCII subset,though in those cases it would be better to usebytes() directly to make the intent clearer,but there is also no downside to just using.char_indices() directly and supporting non-ASCII strings.
You may also want to read thechapter on strings in the Rust Bookwhich goes into this in more detail.
for (idx, c) in s.chars().enumerate() { let _ = s[idx..]; // ⚠️ Panics for strings consisting of multibyte characters}Use instead:
for (idx, c) in s.char_indices() { let _ = s[idx..];}Checks for expressions where a character literal is casttou8 and suggests using a byte literal instead.
In general, casting values to smaller types iserror-prone and should be avoided where possible. In the particular case ofconverting a character literal tou8, it is easy to avoid by just using abyte literal instead. As an added bonus,b'a' is also slightly shorterthan'a' as u8.
'x' as u8A better version, using the byte literal:
b'x'Checks for usage of_.chars().last() or_.chars().next_back() on astr to check if it ends with a given char.
Readability, this can be written more concisely as_.ends_with(_).
name.chars().last() == Some('_') || name.chars().next_back() == Some('-');Use instead:
name.ends_with('_') || name.ends_with('-');Checks for usage of.chars().next() on astr to checkif it starts with a given char.
Readability, this can be written more concisely as_.starts_with(_).
let name = "foo";if name.chars().next() == Some('_') {};Use instead:
let name = "foo";if name.starts_with('_') {};Checks for explicit bounds checking when casting.
Reduces the readability of statements & is error prone.
foo <= i32::MAX as u32;Use instead:
i32::try_from(foo).is_ok();msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage of.drain(..) for the sole purpose of clearing a container.
This creates an unnecessary iterator that is dropped immediately.
Calling.clear() also makes the intent clearer.
let mut v = vec![1, 2, 3];v.drain(..);Use instead:
let mut v = vec![1, 2, 3];v.clear();Checks for usage of.clone() on aCopy type.
The only reasonCopy types implementClone is forgenerics, not for using theclone method on a concrete type.
42u64.clone();Checks for usage of.clone() on a ref-counted pointer,(Rc,Arc,rc::Weak, orsync::Weak), and suggests calling Clone via unifiedfunction syntax instead (e.g.,Rc::clone(foo)).
Calling.clone() on anRc,Arc, orWeakcan obscure the fact that only the pointer is being cloned, not the underlyingdata.
let x = Rc::new(1);x.clone();Use instead:
Rc::clone(&x);Checks for usage ofcloned() on anIterator orOption wherecopied() could be used instead.
copied() is better because it guarantees that the type being clonedimplementsCopy.
[1, 2, 3].iter().cloned();Use instead:
[1, 2, 3].iter().copied();msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for slice references with cloned references such as&[f.clone()].
A reference does not need to be owned in order to be used as a slice.
This lint does not know whether or not a clone implementation has side effects.
let data = 10;let data_ref = &data;take_slice(&[data_ref.clone()]);Use instead:
use std::slice;let data = 10;let data_ref = &data;take_slice(slice::from_ref(data_ref));This lint checks for equality comparisons withptr::null
It’s easier and more readable to use the inherent.is_null()method instead
use std::ptr;if x == ptr::null { // ..}Use instead:
if x.is_null() { // ..}Checks for conversions to owned values just for the sakeof a comparison.
The comparison can operate on a reference, so creatingan owned value effectively throws it away directly afterwards, which isneedlessly consuming code and heap space.
if x.to_owned() == y {}Use instead:
if x == y {}Protects against unintended coercion of references to container types to&dyn Any when thecontainer type dereferences to adyn Any which could be directly referenced instead.
The intention is usually to get a reference to thedyn Any the value dereferences to,rather than coercing a reference to the container itself to&dyn Any.
BecauseBox<dyn Any> itself implementsAny,&Box<dyn Any>can be coerced to an&dyn Any which refers totheBox itself, rather than theinnerdyn Any.
let x: Box<dyn Any> = Box::new(0u32);let dyn_any_of_box: &dyn Any = &x;// Fails as we have a &dyn Any to the Box, not the u32assert_eq!(dyn_any_of_box.downcast_ref::<u32>(), None);Use instead:
let x: Box<dyn Any> = Box::new(0u32);let dyn_any_of_u32: &dyn Any = &*x;// Succeeds since we have a &dyn Any to the inner u32!assert_eq!(dyn_any_of_u32.downcast_ref::<u32>(), Some(&0u32));We used to think it measured how hard a method is to understand.
Ideally, we would like to be able to measure how hard a function isto understand given its context (what we call its Cognitive Complexity).But that’s not what this lint does. See “Known problems”
The true Cognitive Complexity of a method is not something we cancalculate using modern technology. This lint has been left inrestriction so as to not mislead users into using this lint as ameasurement tool.
For more detailed information, seerust-clippy#3793
cognitive-complexity-threshold: The maximum cognitive complexity a function can have
(default:25)
Checks for collapsibleelse { if ... } expressionsthat can be collapsed toelse if ....
Eachif-statement adds one level of nesting, whichmakes code look more complex than it really is.
if x { …} else { if y { … }}Should be written:
if x { …} else if y { …}lint-commented-code: Whether collapsibleif andelse if chains are linted if they contain comments inside the partsthat would be collapsed.
(default:false)
Checks for nestedif statements which can be collapsedby&&-combining their conditions.
Eachif-statement adds one level of nesting, whichmakes code look more complex than it really is.
if x { if y { // … }}Use instead:
if x && y { // …}lint-commented-code: Whether collapsibleif andelse if chains are linted if they contain comments inside the partsthat would be collapsed.
(default:false)
Finds nestedmatch orif let expressions where the patterns may be “collapsed” togetherwithout adding any branches.
Note that this lint is not intended to findall cases where nested match patterns can be merged, but onlycases where merging would most likely make the code more readable.
It is unnecessarily verbose and complex.
fn func(opt: Option<Result<u64, String>>) { let n = match opt { Some(n) => match n { Ok(n) => n, _ => return, } None => return, };}Use instead:
fn func(opt: Option<Result<u64, String>>) { let n = match opt { Some(Ok(n)) => n, _ => return, };}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for consecutive calls tostr::replace (2 or more)that can be collapsed into a single call.
Consecutivestr::replace calls scan the string multiple timeswith repetitive code.
let hello = "hesuo worpd" .replace('s', "l") .replace("u", "l") .replace('p', "l");Use instead:
let hello = "hesuo worpd".replace(['s', 'u', 'p'], "l");msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for collections that are never queried.
Putting effort into constructing a collection but then never querying it might indicate thatthe author forgot to do whatever they intended to do with the collection. Example: Clonea vector, sort it for iteration, but then mistakenly iterate the original vectorinstead.
let mut sorted_samples = samples.clone();sorted_samples.sort();for sample in &samples { // Oops, meant to use `sorted_samples`. println!("{sample}");}Use instead:
let mut sorted_samples = samples.clone();sorted_samples.sort();for sample in &sorted_samples { println!("{sample}");}Checks comparison chains written withif that can berewritten withmatch andcmp.
if is not guaranteed to be exhaustive and conditionals can getrepetitive
The match statement may be slower due to the compilernot inlining the call to cmp. See issue#5354
fn f(x: u8, y: u8) { if x > y { a() } else if x < y { b() } else { c() }}Use instead:
use std::cmp::Ordering;fn f(x: u8, y: u8) { match x.cmp(&y) { Ordering::Greater => a(), Ordering::Less => b(), Ordering::Equal => c() }}Checks for comparing to an empty slice such as"" or[],and suggests using.is_empty() where applicable.
Some structures can answer.is_empty() much fasterthan checking for equality. So it is good to get into the habit of using.is_empty(), and having it is cheap.Besides, it makes the intent clearer than a manual comparison in some contexts.
if s == "" { ..}if arr == [] { ..}Use instead:
if s.is_empty() { ..}if arr.is_empty() { ..}Checks for casts of a primitive method pointer likemax/min to any integer type.
Casting a function pointer to an integer can have surprising results and can occuraccidentally if parentheses are omitted from a function call. If you aren’t doing anythinglow-level with function pointers then you can opt out of casting functions to integers inorder to avoid mistakes. Alternatively, you can use this lint to audit all uses of functionpointer casts in your code.
let _ = u16::max as usize;Use instead:
let _ = u16::MAX as usize;It identifies calls to.is_empty() on constant values.
String literals and constant values are known at compile time. Checking if theyare empty will always return the same value. This might not be the intention ofthe expression.
let value = "";if value.is_empty() { println!("the string is empty");}Use instead:
println!("the string is empty");Checks for types that implementCopy as well asIterator.
Implicit copies can be confusing when working withiterator combinators.
#[derive(Copy, Clone)]struct Countdown(u8);impl Iterator for Countdown { // ...}let a: Vec<_> = my_iterator.take(1).collect();let b: Vec<_> = my_iterator.collect();Checks for usage ofcrate as opposed to$crate in a macro definition.
crate refers to the macro call’s crate, whereas$crate refers to the macro definition’scrate. Rarely is the former intended. See:https://doc.rust-lang.org/reference/macros-by-example.html#hygiene
#[macro_export]macro_rules! print_message { () => { println!("{}", crate::MESSAGE); };}pub const MESSAGE: &str = "Hello!";Use instead:
#[macro_export]macro_rules! print_message { () => { println!("{}", $crate::MESSAGE); };}pub const MESSAGE: &str = "Hello!";Note that if the use ofcrate is intentional, anallow attribute can be applied to themacro definition, e.g.:
#[allow(clippy::crate_in_macro_def)]macro_rules! ok { ... crate::foo ... }Checks usage ofstd::fs::create_dir and suggest usingstd::fs::create_dir_all instead.
Sometimesstd::fs::create_dir is mistakenly chosen overstd::fs::create_dir_all,resulting in failure when more than one directory needs to be created or when the directory already exists.Crates which never need to specifically create a single directory may wish to prevent this mistake.
std::fs::create_dir("foo");Use instead:
std::fs::create_dir_all("foo");Checks for transmutes between a typeT and*T.
It’s easy to mistakenly transmute between a type and apointer to that type.
core::intrinsics::transmute(t) // where the result type is the same as // `*t` or `&t`'sChecks for usage of thedbg! macro.
Thedbg! macro is intended as a debugging tool. It should not be present in releasedsoftware or committed to a version control system.
dbg!(true)Use instead:
trueallow-dbg-in-tests: Whetherdbg! should be allowed in test functions or#[cfg(test)]
(default:false)
Checks for function/method calls with a mutableparameter indebug_assert!,debug_assert_eq! anddebug_assert_ne! macros.
In release buildsdebug_assert! macros are optimized out by thecompiler.Therefore mutating something in adebug_assert! macro results in different behaviorbetween a release and debug build.
debug_assert_eq!(vec![3].pop(), Some(3));// ordebug_assert!(takes_a_mut_parameter(&mut x));Warns if there is a better representation for a numeric literal.
Especially for big powers of 2, a hexadecimal representation is usually morereadable than a decimal representation.
`255` => `0xFF``65_535` => `0xFFFF``4_042_322_160` => `0xF0F0_F0F0`literal-representation-threshold: The lower bound for linting decimal literals
(default:16384)
Checks for the declaration of named constant which contain interior mutability.
Named constants are copied at every use site which means any change to their valuewill be lost after the newly created value is dropped. e.g.
use core::sync::atomic::{AtomicUsize, Ordering};const ATOMIC: AtomicUsize = AtomicUsize::new(0);fn add_one() -> usize { // This will always return `0` since `ATOMIC` is copied before it's used. ATOMIC.fetch_add(1, Ordering::AcqRel)}If shared modification of the value is desired, astatic item is needed instead.If that is not desired, aconst fn constructor should be used to make it obviousat the use site that a new value is created.
Prior toconst fn stabilization this was the only way to provide a value whichcould initialize astatic item (e.g. thestd::sync::ONCE_INIT constant). Inthis case the use ofconst is required and this lint should be suppressed.
There also exists types which contain private fields with interior mutability, butno way to both create a value as a constant and modify any mutable field using thetype’s public interface (e.g.bytes::Bytes). As there is no reasonable way toscan a crate’s interface to see if this is the case, all such types will be linted.If this happens use theignore-interior-mutability configuration option to allowthe type.
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchangedassert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinctUse instead:
static STATIC_ATOM: AtomicUsize = AtomicUsize::new(15);STATIC_ATOM.store(9, SeqCst);assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instanceignore-interior-mutability: A list of paths to types that should be treated as if they do not contain interior mutability
(default:["bytes::Bytes"])
Checks for construction on unit struct usingdefault.
This adds code complexity and an unnecessary function call.
#[derive(Default)]struct S<T> { _marker: PhantomData<T>}let _: S<i32> = S { _marker: PhantomData::default()};Use instead:
struct S<T> { _marker: PhantomData<T>}let _: S<i32> = S { _marker: PhantomData};It checks forstd::iter::Empty::default() and suggests replacing it withstd::iter::empty().
std::iter::empty() is the more idiomatic way.
let _ = std::iter::Empty::<usize>::default();let iter: std::iter::Empty<usize> = std::iter::Empty::default();Use instead:
let _ = std::iter::empty::<usize>();let iter: std::iter::Empty<usize> = std::iter::empty();Checks for usage of unconstrained numeric literals which may cause default numeric fallback in typeinference.
Default numeric fallback means that if numeric types have not yet been bound to concretetypes at the end of type inference, then integer type is bound toi32, and similarlyfloating type is bound tof64.
SeeRFC0212 for more information about the fallback.
To ensure that every numeric type is chosen explicitly rather than implicitly.
This lint is implemented using a custom algorithm independent of rustc’s inference,which results in many false positives and false negatives.
let i = 10;let f = 1.23;Use instead:
let i = 10_i32;let f = 1.23_f64;Checks for literal calls toDefault::default().
It’s easier for the reader if the name of the type is used, rather than thegenericDefault.
let s: String = Default::default();Use instead:
let s = String::default();Displays a warning when a union is declared with the default representation (without a#[repr(C)] attribute).
Unions in Rust have unspecified layout by default, despite many people thinking that theylay out each field at the start of the union (like C does). That is, there are no guaranteesabout the offset of the fields for unions with multiple non-ZST fields without an explicitlyspecified layout. These cases may lead to undefined behavior in unsafe blocks.
union Foo { a: i32, b: u32,}fn main() { let _x: u32 = unsafe { Foo { a: 0_i32 }.b // Undefined behavior: `b` is allowed to be padding };}Use instead:
#[repr(C)]union Foo { a: i32, b: u32,}fn main() { let _x: u32 = unsafe { Foo { a: 0_i32 }.b // Now defined behavior, this is just an i32 -> u32 transmute };}Checks for#[cfg_attr(rustfmt, rustfmt_skip)] and suggests to replace itwith#[rustfmt::skip].
Since tool_attributes (rust-lang/rust#44690)are stable now, they should be used instead of the oldcfg_attr(rustfmt) attributes.
This lint doesn’t detect crate level inner attributes, because they getprocessed before the PreExpansionPass lints get executed. See#3123
#[cfg_attr(rustfmt, rustfmt_skip)]fn main() { }Use instead:
#[rustfmt::skip]fn main() { }msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for#[cfg_attr(feature = "cargo-clippy", ...)] and for#[cfg(feature = "cargo-clippy")] and suggests to replace it with#[cfg_attr(clippy, ...)] or#[cfg(clippy)].
This feature has been deprecated for years and shouldn’t be used anymore.
#[cfg(feature = "cargo-clippy")]struct Bar;Use instead:
#[cfg(clippy)]struct Bar;Checks for#[deprecated] annotations with asincefield that is not a valid semantic version. Also allows “TBD” to signalfuture deprecation.
For checking the version of the deprecation, it must bea valid semver. Failing that, the contained information is useless.
#[deprecated(since = "forever")]fn something_else() { /* ... */ }Checks for usage of*& and*&mut in expressions.
Immediately dereferencing a reference is no-op andmakes the code less clear.
Multiple dereference/addrof pairs are not handled sothe suggested fix forx = **&&y isx = *&y, which is still incorrect.
let a = f(*&mut b);let c = *&d;Use instead:
let a = f(b);let c = d;Checks for slicing expressions which are equivalent to dereferencing thevalue.
Some people may prefer to dereference rather than slice.
let vec = vec![1, 2, 3];let slice = &vec[..];Use instead:
let vec = vec![1, 2, 3];let slice = &*vec;Detects manualstd::default::Default implementations that are identical to a derived implementation.
It is less concise.
struct Foo { bar: bool}impl Default for Foo { fn default() -> Self { Self { bar: false } }}Use instead:
#[derive(Default)]struct Foo { bar: bool}Derive macrossometimes use incorrect boundsin generic types and the user definedimpl may be more generalized orspecialized than what derive will produce. This lint can’t detect the manualimplhas exactly equal bounds, and therefore this lint is disabled for types withgeneric parameters.
msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Lints against manualPartialOrd andOrd implementations for types with a derivedOrdorPartialOrd implementation.
The implementation of these traits must agree (forexample for use withsort) so it’s probably a bad idea to use adefault-generatedOrd implementation with an explicitly definedPartialOrd. In particular, the following must hold for any typeimplementingOrd:
k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap()#[derive(Ord, PartialEq, Eq)]struct Foo;impl PartialOrd for Foo { ...}Use instead:
#[derive(PartialEq, Eq)]struct Foo;impl PartialOrd for Foo { fn partial_cmp(&self, other: &Foo) -> Option<Ordering> { Some(self.cmp(other)) }}impl Ord for Foo { ...}or, if you don’t need a custom ordering:
#[derive(Ord, PartialOrd, PartialEq, Eq)]struct Foo;Checks for types that derivePartialEq and could implementEq.
If a typeT derivesPartialEq and all of its members implementEq,thenT can always implementEq. ImplementingEq allowsT to be usedin APIs that requireEq types. It also allows structs containingT to deriveEq themselves.
#[derive(PartialEq)]struct Foo { i_am_eq: i32, i_am_eq_too: Vec<String>,}Use instead:
#[derive(PartialEq, Eq)]struct Foo { i_am_eq: i32, i_am_eq_too: Vec<String>,}Lints against manualPartialEq implementations for types with a derivedHashimplementation.
The implementation of these traits must agree (forexample for use withHashMap) so it’s probably a bad idea to use adefault-generatedHash implementation with an explicitly definedPartialEq. In particular, the following must hold for any type:
k1 == k2 ⇒ hash(k1) == hash(k2)#[derive(Hash)]struct Foo;impl PartialEq for Foo { ...}Denies the configured macros in clippy.toml
Note: Even though this lint is warn-by-default, it will only trigger ifmacros are defined in the clippy.toml file.
Some macros are undesirable in certain contexts, and it’s beneficial tolint for them as needed.
An example clippy.toml configuration:
disallowed-macros = [ # Can use a string as the path of the disallowed macro. "std::print", # Can also use an inline table with a `path` key. { path = "std::println" }, # When using an inline table, can add a `reason` for why the macro # is disallowed. { path = "serde::Serialize", reason = "no serializing" }, # This would normally error if the path is incorrect, but with `allow-invalid` = `true`, # it will be silently ignored { path = "std::invalid_macro", reason = "use alternative instead", allow-invalid = true }]use serde::Serialize;println!("warns");// The diagnostic will contain the message "no serializing"#[derive(Serialize)]struct Data { name: String, value: usize,}disallowed-macros: The list of disallowed macros, written as fully qualified paths.Fields:
path (required): the fully qualified path to the macro that should be disallowed
reason (optional): explanation why this macro is disallowed
replacement (optional): suggested alternative macro
allow-invalid (optional,false by default): when set totrue, it will ignore this entryif the path doesn’t exist, instead of emitting an error
(default:[])
Denies the configured methods and functions in clippy.toml
Note: Even though this lint is warn-by-default, it will only trigger ifmethods are defined in the clippy.toml file.
Some methods are undesirable in certain contexts, and it’s beneficial tolint for them as needed.
An example clippy.toml configuration:
disallowed-methods = [ # Can use a string as the path of the disallowed method. "std::boxed::Box::new", # Can also use an inline table with a `path` key. { path = "std::time::Instant::now" }, # When using an inline table, can add a `reason` for why the method # is disallowed. { path = "std::vec::Vec::leak", reason = "no leaking memory" }, # Can also add a `replacement` that will be offered as a suggestion. { path = "std::sync::Mutex::new", reason = "prefer faster & simpler non-poisonable mutex", replacement = "parking_lot::Mutex::new" }, # This would normally error if the path is incorrect, but with `allow-invalid` = `true`, # it will be silently ignored { path = "std::fs::InvalidPath", reason = "use alternative instead", allow-invalid = true },]let xs = vec![1, 2, 3, 4];xs.leak(); // Vec::leak is disallowed in the config.// The diagnostic contains the message "no leaking memory".let _now = Instant::now(); // Instant::now is disallowed in the config.let _box = Box::new(3); // Box::new is disallowed in the config.Use instead:
let mut xs = Vec::new(); // Vec::new is _not_ disallowed in the config.xs.push(123); // Vec::push is _not_ disallowed in the config.disallowed-methods: The list of disallowed methods, written as fully qualified paths.Fields:
path (required): the fully qualified path to the method that should be disallowed
reason (optional): explanation why this method is disallowed
replacement (optional): suggested alternative method
allow-invalid (optional,false by default): when set totrue, it will ignore this entryif the path doesn’t exist, instead of emitting an error
(default:[])
Checks for usage of disallowed names for variables, suchasfoo.
These names are usually placeholder names and should beavoided.
let foo = 3.14;disallowed-names: The list of disallowed names to lint about. NB:bar is not here since it has legitimate uses. The value".." can be used as part of the list to indicate that the configured values should be appended to thedefault configuration of Clippy. By default, any configuration will replace the default value.
(default:["foo", "baz", "quux"])
Checks for usage of unicode scripts other than those explicitly allowedby the lint config.
This lint doesn’t take into account non-text scripts such asUnknown andLinear_A.It also ignores theCommon script type.While configuring, be sure to use official script namealiases fromthe list of supported scripts.
See also:non_ascii_idents.
It may be not desired to have many different scripts foridentifiers in the codebase.
Note that if you only want to allow typical English, you might want to usebuilt-innon_ascii_idents lint instead.
// Assuming that `clippy.toml` contains the following line:// allowed-scripts = ["Latin", "Cyrillic"]let counter = 10; // OK, latin is allowed.let счётчик = 10; // OK, cyrillic is allowed.let zähler = 10; // OK, it's still latin.let カウンタ = 10; // Will spawn the lint.allowed-scripts: The list of unicode scripts allowed to be used in the scope.
(default:["Latin"])
Denies the configured types in clippy.toml.
Note: Even though this lint is warn-by-default, it will only trigger iftypes are defined in the clippy.toml file.
Some types are undesirable in certain contexts.
An example clippy.toml configuration:
disallowed-types = [ # Can use a string as the path of the disallowed type. "std::collections::BTreeMap", # Can also use an inline table with a `path` key. { path = "std::net::TcpListener" }, # When using an inline table, can add a `reason` for why the type # is disallowed. { path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" }, # Can also add a `replacement` that will be offered as a suggestion. { path = "std::sync::Mutex", reason = "prefer faster & simpler non-poisonable mutex", replacement = "parking_lot::Mutex" }, # This would normally error if the path is incorrect, but with `allow-invalid` = `true`, # it will be silently ignored { path = "std::invalid::Type", reason = "use alternative instead", allow-invalid = true }]use std::collections::BTreeMap;// or its uselet x = std::collections::BTreeMap::new();Use instead:
// A similar type that is allowed by the configuse std::collections::HashMap;disallowed-types: The list of disallowed types, written as fully qualified paths.Fields:
path (required): the fully qualified path to the type that should be disallowed
reason (optional): explanation why this type is disallowed
replacement (optional): suggested alternative type
allow-invalid (optional,false by default): when set totrue, it will ignore this entryif the path doesn’t exist, instead of emitting an error
(default:[])
Checks for diverging calls that are not match arms orstatements.
It is often confusing to read. In addition, thesub-expression evaluation order for Rust is not well documented.
Someone might want to usesome_bool || panic!() as ashorthand.
let a = b() || panic!() || c();// `c()` is dead, `panic!()` is only called if `b()` returns `false`let x = (a, b, c, panic!());// can simply be replaced by `panic!()`Checks the doc comments have unbroken links, mostly causedby bad formatted links such as broken across multiple lines.
Because documentation generated by rustdoc will be brokensince expected links won’t be links and just text.
This link is broken:
/// [example of a bad link](https:///// github.com/rust-lang/rust-clippy/)pub fn do_something() {}It shouldn’t be broken across multiple lines to work:
/// [example of a good link](https://github.com/rust-lang/rust-clippy/)pub fn do_something() {}Detects doc comment linebreaks that use double spaces to separate lines, instead of back-slash (\).
Double spaces, when used as doc comment linebreaks, can be difficult to see, and mayaccidentally be removed during automatic formatting or manual refactoring. The use of a back-slash (\)is clearer in this regard.
The two replacement dots in this example represent a double space.
/// This command takes two numbers as inputs and··/// adds them together, and then returns the result.fn add(l: i32, r: i32) -> i32 { l + r}Use instead:
/// This command takes two numbers as inputs and\/// adds them together, and then returns the result.fn add(l: i32, r: i32) -> i32 { l + r}Checks if included files in doc comments are included only forcfg(doc).
These files are not useful for compilation but will still be included.Also, if any of these non-source code file is updated, it will trigger arecompilation.
Excluding this will currently result in the file being left out ifthe item’s docs are inlined from another crate. This may be fixed in afuture version of rustdoc.
#![doc = include_str!("some_file.md")]Use instead:
#![cfg_attr(doc, doc = include_str!("some_file.md"))]In CommonMark Markdown, the language used to write doc comments, aparagraph nested within a list or block quote does not need any lineafter the first one to be indented or marked. The specification callsthis a “lazy paragraph continuation.”
This is easy to write but hard to read. Lazy continuations makesunintended markers hard to see, and make it harder to deduce thedocument’s intended structure.
This table is probably intended to have two rows,but it does not. It has zero rows, and is followed bya block quote.
/// Range | Description/// ----- | -----------/// >= 1 | fully opaque/// < 1 | partially see-throughfn set_opacity(opacity: f32) {}Fix it by escaping the marker:
/// Range | Description/// ----- | -----------/// \>= 1 | fully opaque/// < 1 | partially see-throughfn set_opacity(opacity: f32) {}This example is actually intended to be a list:
/// * Do nothing./// * Then do something. Whatever it is needs done,/// it should be done right now.Fix it by indenting the list contents:
/// * Do nothing./// * Then do something. Whatever it is needs done,/// it should be done right now.Checks for links with code directly adjacent to code text:[`MyItem`]`<`[`u32`]`>`.
It can be written more simply using HTML-style<code> tags.
//! [`first`](x)`second`Use instead:
//! <code>[first](x)second</code>Detects the syntax['foo'] in documentation comments (notice quotes instead of backticks)outside of code blocks
It is likely a typo when defining an intra-doc link
/// See also: ['foo']fn bar() {}Use instead:
/// See also: [`foo`]fn bar() {}Checks for the presence of_,:: or camel-case wordsoutside ticks in documentation.
Rustdoc supports markdown formatting,_,:: andcamel-case probably indicates some code which should be included betweenticks._ can also be used for emphasis in markdown, this lint tries toconsider that.
Lots of bad docs won’t be fixed, what the lint checksfor is limited, and there are still false positives. HTML elements and theircontent are not linted.
In addition, when writing documentation comments, including[] bracketsinside a link text would trip the parser. Therefore, documenting link with[SmallVec<[T; INLINE_CAPACITY]>] and then [SmallVec<[T; INLINE_CAPACITY]>]: SmallVecwould fail.
/// Do something with the foo_bar parameter. See also/// that::other::module::foo.// ^ `foo_bar` and `that::other::module::foo` should be ticked.fn doit(foo_bar: usize) {}// Link text with `[]` brackets should be written as following:/// Consume the array and return the inner/// [`SmallVec<[T; INLINE_CAPACITY]>`][SmallVec]./// [SmallVec]: SmallVecfn main() {}doc-valid-idents: The list of words this lint should not consider as identifiers needing ticks. The value".." can be used as part of the list to indicate, that the configured values should be appended to thedefault configuration of Clippy. By default, any configuration will replace the default value. For example:doc-valid-idents = ["ClipPy"] would replace the default list with["ClipPy"].
doc-valid-idents = ["ClipPy", ".."] would appendClipPy to the default list.
(default:["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "InfiniBand", "RoCE", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"])
Warns if a link reference definition appears at the start of alist item or quote.
This is probably intended as an intra-doc link. If it is reallysupposed to be a reference definition, it can be written outsideof the list item or quote.
//! - [link]: descriptionUse instead:
//! - [link][]: description (for intra-doc link)//!//! [link]: destination (for link reference definition)Detects overindented list items in doc comments where the continuationlines are indented more than necessary.
Overindented list items in doc comments can lead to inconsistent andpoorly formatted documentation when rendered. Excessive indentation maycause the text to be misinterpreted as a nested list item or code block,affecting readability and the overall structure of the documentation.
/// - This is the first item in a list/// and this line is overindented.Fixes this into:
/// - This is the first item in a list/// and this line is overindented.Detects syntax that looks like a footnote reference.
Rustdoc footnotes are compatible with GitHub-Flavored Markdown (GFM).GFM does not parse a footnote reference unless its definition alsoexists. This lint checks for footnote references with missingdefinitions, unless it thinks you’re writing a regex.
This probably means that a footnote was meant to exist,but was not written.
/// This is not a footnote[^1], because no definition exists.fn my_fn() {}Use instead:
/// This is a footnote[^1].////// [^1]: defined herefn my_fn() {}Checks for double comparisons that could be simplified to a single expression.
Readability.
if x == y || x < y {}Use instead:
if x <= y {}Checks forIterator::last being called on aDoubleEndedIterator, which can be replacedwithDoubleEndedIterator::next_back.
Iterator::last is implemented by consuming the iterator, which is unnecessary ifthe iterator is aDoubleEndedIterator. Since Rust traits do not allow specialization,Iterator::last cannot be optimized forDoubleEndedIterator.
let last_arg = "echo hello world".split(' ').last();Use instead:
let last_arg = "echo hello world".split(' ').next_back();Checks for a#[must_use] attribute withoutfurther information on functions and methods that return a type alreadymarked as#[must_use].
The attribute isn’t needed. Not using the resultwill already be reported. Alternatively, one can add some text to theattribute to improve the lint message.
#[must_use]fn double_must_use() -> Result<(), ()> { unimplemented!();}Checks for unnecessary double parentheses.
This makes code harder to read and might indicate amistake.
fn simple_double_parens() -> i32 { ((0))}foo((0));Use instead:
fn simple_no_parens() -> i32 { (0)}foo(0);Checks for calls to.drain() that clear the collection, immediately followed by a call to.collect().
“Collection” in this context refers to any type with a
drainmethod:Vec,VecDeque,BinaryHeap,HashSet,HashMap,String
Usingmem::take is faster as it avoids the allocation.When usingmem::take, the old collection is replaced with an empty one and ownership ofthe old collection is returned.
mem::take(&mut vec) is almost equivalent tovec.drain(..).collect(), except thatit also moves thecapacity. The user might have explicitly written it this wayto keep the capacity on the originalVec.
fn remove_all(v: &mut Vec<i32>) -> Vec<i32> { v.drain(..).collect()}Use instead:
use std::mem;fn remove_all(v: &mut Vec<i32>) -> Vec<i32> { mem::take(v)}Checks for calls tostd::mem::drop with a value that does not implementDrop.
Callingstd::mem::drop is no different than dropping such a type. A different value mayhave been intended.
struct Foo;let x = Foo;std::mem::drop(x);Checks for files that are included as modules multiple times.
Loading a file as a module more than once causes it to be compiledmultiple times, taking longer and putting duplicate content into themodule tree.
// lib.rsmod a;mod b;// a.rs#[path = "./b.rs"]mod b;Use instead:
// lib.rsmod a;mod b;// a.rsuse crate::b;Checks for function arguments having the similar namesdiffering by an underscore.
It affects code readability.
fn foo(a: i32, _a: i32) {}Use instead:
fn bar(a: i32, _b: i32) {}Checks for attributes that appear two or more times.
Repeating an attribute on the same item (or globally on the same crate)is unnecessary and doesn’t have an effect.
#[allow(dead_code)]#[allow(dead_code)]fn foo() {}Use instead:
#[allow(dead_code)]fn foo() {}Checks for calculation of subsecond microseconds or millisecondsfrom otherDuration methods.
It’s more concise to callDuration::subsec_micros() orDuration::subsec_millis() than to calculate them.
let micros = duration.subsec_nanos() / 1_000;let millis = duration.subsec_nanos() / 1_000_000;Use instead:
let micros = duration.subsec_micros();let millis = duration.subsec_millis();Checks for integer validity checks, followed by a transmute that is (incorrectly) evaluatedeagerly (e.g. usingbool::then_some).
Eager evaluation means that thetransmute call is executed regardless of whether the condition is true or false.This can introduce unsoundness and other subtle bugs.
Consider the following function which is meant to convert an unsigned integer to its enum equivalent via transmute.
#[repr(u8)]enum Opcode { Add = 0, Sub = 1, Mul = 2, Div = 3}fn int_to_opcode(op: u8) -> Option<Opcode> { (op < 4).then_some(unsafe { std::mem::transmute(op) })}This may appear fine at first given that it checks that theu8 is within the validity range of the enum,however the transmute is evaluated eagerly, meaning that it executes even ifop >= 4!
This makes the function unsound, because it is possible for the caller to cause undefined behavior(creating an enum with an invalid bitpattern) entirely in safe code only by passing an incorrect value,which is normally only a bug that is possible in unsafe code.
One possible way in which this can go wrong practically is that the compiler sees it as:
let temp: Foo = unsafe { std::mem::transmute(op) };(0 < 4).then_some(temp)and optimizes away the(0 < 4) check based on the assumption that since aFoo was created fromop with the validity range0..3,it isimpossible for this condition to be false.
In short, it is possible for this function to be optimized in a way that makes itnever returnNone,even if passed the value4.
This can be avoided by instead using lazy evaluation. For the example above, this should be written:
fn int_to_opcode(op: u8) -> Option<Opcode> { (op < 4).then(|| unsafe { std::mem::transmute(op) }) ^^^^ ^^ `bool::then` only executes the closure if the condition is true!}Checks for lifetime annotations which can be replaced with anonymous lifetimes ('_).
The additional lifetimes can make the code look more complicated.
This lint ignores functions withwhere clauses that referencelifetimes to prevent false positives.
fn f<'a>(x: &'a str) -> Chars<'a> { x.chars()}Use instead:
fn f(x: &str) -> Chars<'_> { x.chars()}Checks for usage of if expressions with anelse if branch,but without a finalelse branch.
Some coding guidelines require this (e.g., MISRA-C:2004 Rule 14.10).
if x.is_positive() { a();} else if x.is_negative() { b();}Use instead:
if x.is_positive() { a();} else if x.is_negative() { b();} else { // We don't care about zero.}Detects documentation that is empty.
Empty docs clutter code without adding value, reducing readability and maintainability.
///fn returns_true() -> bool { true}Use instead:
fn returns_true() -> bool { true}Checks for emptyDrop implementations.
EmptyDrop implementations have no effect when dropping an instance of the type. They aremost likely useless. However, an emptyDrop implementation prevents a type from beingdestructured, which might be the intention behind adding the implementation as a marker.
struct S;impl Drop for S { fn drop(&mut self) {}}Use instead:
struct S;Finds enum variants without fields that are declared with empty brackets.
Empty brackets after a enum variant declaration are redundant and can be omitted,and it may be desirable to do so consistently for style.
However, removing the brackets also introduces a public constant named after the variant,so this is not just a syntactic simplification but an API change, and adding them backis abreaking API change.
enum MyEnum { HasData(u8), HasNoData(), // redundant parentheses NoneHereEither {}, // redundant braces}Use instead:
enum MyEnum { HasData(u8), HasNoData, NoneHereEither,}Checks forenums with no variants, which therefore are uninhabited types(cannot be instantiated).
As of this writing, thenever_type is still a nightly-only experimental API.Therefore, this lint is only triggered if#![feature(never_type)] is enabled.
If you only want a type which can’t be instantiated, you should use!(the primitive type “never”), because! has more extensive compiler support(type inference, etc.) and implementations of common traits.
If you need to introduce a distinct type, consider using anewtypestructcontaining! instead (struct MyType(pub !)), because it is more idiomaticto use astruct rather than anenum when anenum is unnecessary.
If you do this, note that thevisibility of the! field determines whetherthe uninhabitedness is visible in documentation, and whether it can be patternmatched to mark code unreachable. If the field is not visible, then the structacts like any other struct with private fields.
For further information, visitthe never type’s documentation.
enum CannotExist {}Use instead:
#![feature(never_type)]/// Use the `!` type directly...type CannotExist = !;/// ...or define a newtype which is distinct.struct CannotExist2(pub !);Checks for empty lines after doc comments.
The doc comment may have meant to be an inner doc comment, regularcomment or applied to some old code that is now commented out. If it wasintended to be a doc comment, then the empty line should be removed.
/// Some doc comment with a blank line after it.fn f() {}/// Docs for `old_code`// fn old_code() {}fn new_code() {}Use instead:
//! Convert it to an inner doc comment// Or a regular comment/// Or remove the empty linefn f() {}// /// Docs for `old_code`// fn old_code() {}fn new_code() {}Checks for empty lines after outer attributes
The attribute may have meant to be an inner attribute (#![attr]). Ifit was meant to be an outer attribute (#[attr]) then the empty lineshould be removed
#[allow(dead_code)]fn not_quite_good_code() {}Use instead:
// Good (as inner attribute)#![allow(dead_code)]fn this_is_fine() {}// or// Good (as outer attribute)#[allow(dead_code)]fn this_is_fine_too() {}Checks for emptyloop expressions.
These busy loops burn CPU cycles without doinganything. It isalmost always a better idea topanic! than to havea busy loop.
If panicking isn’t possible, think of the environment and either:
Forstd targets, this can be done withstd::thread::sleeporstd::thread::yield_now.
Forno_std targets, doing this is more complicated, especially because#[panic_handler]s can’t panic. To stop/pause the thread, you willprobably need to invoke some target-specific intrinsic. Examples include:
loop {}Finds structs without fields (a so-called “empty struct”) that are declared with brackets.
Empty brackets after a struct declaration can be omitted,and it may be desirable to do so consistently for style.
However, removing the brackets also introduces a public constant named after the struct,so this is not just a syntactic simplification but an API change, and adding them backis abreaking API change.
struct Cookie {}struct Biscuit();Use instead:
struct Cookie;struct Biscuit;Checks for C-like enumerations that arerepr(isize/usize) and have values that don’t fit into ani32.
This will truncate the variant value on 32 bitarchitectures, but works fine on 64 bit.
#[repr(usize)]enum NonPortable { X = 0x1_0000_0000, Y = 0,}Checks foruse Enum::*.
It is usually better style to use the prefixed name ofan enumeration variant, rather than importing variants.
Old-style enumerations that prefix the variants arestill around.
use std::cmp::Ordering::*;foo(Less);Use instead:
use std::cmp::Ordering;foo(Ordering::Less)Detects enumeration variants that are prefixed or suffixedby the same characters.
Enumeration variant names should specify their variant,not repeat the enumeration name.
Characters with no casing will be considered when comparing prefixes/suffixesThis applies to numbers and non-ascii characters without casinge.g.Foo1 andFoo2 is considered to have different prefixes(the prefixes areFoo1 andFoo2 respectively), as alsoBar螃,Bar蟹
enum Cake { BlackForestCake, HummingbirdCake, BattenbergCake,}Use instead:
enum Cake { BlackForest, Hummingbird, Battenberg,}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
enum-variant-name-threshold: The minimum number of enum variants for the lints about variant names to trigger
(default:3)
Checks for equal operands to comparison, logical andbitwise, difference and division binary operators (==,>, etc.,&&,||,&,|,^,- and/).
This is usually just a typo or a copy and paste error.
False negatives: We had some false positives regardingcalls (notablyracer had one instanceofx.pop() && x.pop()), so we removed matching any function or methodcalls. We may introduce a list of known pure functions in the future.
if x + 1 == x + 1 {}// orassert_eq!(a, a);Checks for pattern matchings that can be expressed using equality.
&& and|| andreuse if blocksif let Some(2) = x { do_thing();}Use instead:
if x == Some(2) { do_thing();}Checks for erasing operations, e.g.,x * 0.
The whole expression can be replaced by zero.This is most likely not the intended outcome and should probably becorrected
let x = 1;0 / x;0 * x;x & 0;Checks for.err().expect() calls on theResult type.
.expect_err() can be called directly to avoid the extra type conversion fromerr().
let x: Result<u32, &str> = Ok(10);x.err().expect("Testing err().expect()");Use instead:
let x: Result<u32, &str> = Ok(10);x.expect_err("Testing expect_err");msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for types namedError that implementError.
It can become confusing when a codebase has 20 types all namedError, requiring eitheraliasing them in theuse statement or qualifying them likemy_module::Error. Thishinders comprehension, as it requires you to memorize every variation of importingErrorused across a codebase.
#[derive(Debug)]pub enum Error { ... }impl std::fmt::Display for Error { ... }impl std::error::Error for Error { ... }Checks for blocks which are nested beyond a certain threshold.
Note: Even though this lint is warn-by-default, it will only trigger if a maximum nesting level is defined in the clippy.toml file.
It can severely hinder readability.
An example clippy.toml configuration:
excessive-nesting-threshold = 3// lib.rspub mod a { pub struct X; impl X { pub fn run(&self) { if true { // etc... } } }}Use instead:
// a.rsfn private_run(x: &X) { if true { // etc... }}pub struct X;impl X { pub fn run(&self) { private_run(self); }}// lib.rspub mod a;excessive-nesting-threshold: The maximum amount of nesting a block can reside in
(default:0)
Checks for float literals with a precision greaterthan that supported by the underlying type.
The lint is suppressed for literals with overconst_literal_digits_threshold digits.
Rust will truncate the literal silently.
let v: f32 = 0.123_456_789_9;println!("{}", v); // 0.123_456_789Use instead:
let v: f64 = 0.123_456_789_9;println!("{}", v); // 0.123_456_789_9const-literal-digits-threshold: The minimum digits a const float literal must have to supress theexcessive_precicion lint
(default:30)
Warns on any exportedenums that are not tagged#[non_exhaustive]
Making anenum exhaustive is a stability commitment: adding a variant is a breaking change.A project may wish to ensure that there are no exhaustive enums or that every exhaustiveenum is explicitly#[allow]ed.
enum Foo { Bar, Baz}Use instead:
#[non_exhaustive]enum Foo { Bar, Baz}Warns on any exportedstructs that are not tagged#[non_exhaustive]
Making astruct exhaustive is a stability commitment: adding a field is a breaking change.A project may wish to ensure that there are no exhaustive structs or that every exhaustivestruct is explicitly#[allow]ed.
struct Foo { bar: u8, baz: String,}Use instead:
#[non_exhaustive]struct Foo { bar: u8, baz: String,}Detects calls to theexit() function that are not in themain function. Calls toexit()immediately terminate the program.
exit() immediately terminates the program with no information other than an exit code.This provides no means to troubleshoot a problem, and may be an unexpected side effect.
Codebases may use this lint to require that all exits are performed either by panicking(which produces a message, a code location, and optionally a backtrace)or by callingexit() frommain() (which is a single place to look).
fn main() { std::process::exit(0);}fn main() { other_function();}fn other_function() { std::process::exit(0);}Use instead:
// To provide a stacktrace and additional informationpanic!("message");// or a main method with a returnfn main() -> Result<(), i32> { Ok(())}Checks for calls to.expect(&format!(...)),.expect(foo(..)),etc., and suggests to useunwrap_or_else instead
The function will always be called.
If the function has side-effects, not calling it willchange the semantics of the program, but you shouldn’t rely on that anyway.
foo.expect(&format!("Err {}: {}", err_code, err_msg));// orfoo.expect(format!("Err {}: {}", err_code, err_msg).as_str());Use instead:
foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg));Checks for.expect() or.expect_err() calls onResults and.expect() call onOptions.
Usually it is better to handle theNone orErr case.Still, for a lot of quick-and-dirty code,expect is a good choice, which is whythis lint isAllow by default.
result.expect() will let the thread panic onErrvalues. Normally, you want to implement more sophisticated error handling,and propagate errors upwards with? operator.
option.expect("one");result.expect("one");Use instead:
option?;// orresult?;allow-expect-in-consts: Whetherexpect should be allowed in code always evaluated at compile time
(default:true)
allow-expect-in-tests: Whetherexpect should be allowed in test functions or#[cfg(test)]
(default:false)
Checks for explicitClone implementations forCopytypes.
To avoid surprising behavior, these traits shouldagree and the behavior ofCopy cannot be overridden. In almost allsituations aCopy type should have aClone implementation that doesnothing more than copy the object, which is what#[derive(Copy, Clone)]gets you.
#[derive(Copy)]struct Foo;impl Clone for Foo { // ..}Checks for dereferencing expressions which would be covered by auto-deref.
This unnecessarily complicates the code.
let x = String::new();let y: &str = &*x;Use instead:
let x = String::new();let y: &str = &x;Checksfor loops over slices with an explicit counterand suggests the use of.enumerate().
Using.enumerate() makes the intent more clear,declutters the code and may be faster in some instances.
let mut i = 0;for item in &v { bar(i, *item); i += 1;}Use instead:
for (i, item) in v.iter().enumerate() { bar(i, *item); }Checks for explicitderef() orderef_mut() method calls.
Dereferencing by&*x or&mut *x is clearer and more concise,when not part of a method chain.
use std::ops::Deref;let a: &mut String = &mut String::from("foo");let b: &str = a.deref();Use instead:
let a: &mut String = &mut String::from("foo");let b = &*a;This lint excludes all of:
let _ = d.unwrap().deref();let _ = Foo::deref(&foo);let _ = <Foo as Deref>::deref(&foo);Checks for loops ony.into_iter() wherey will do, andsuggests the latter.
Readability.
// with `y` a `Vec` or slice:for x in y.into_iter() { // ..}can be rewritten to
for x in y { // ..}Checks for loops onx.iter() where&x will do, andsuggests the latter.
Readability.
False negatives. We currently only warn on some knowntypes.
// with `y` a `Vec` or slice:for x in y.iter() { // ..}Use instead:
for x in &y { // ..}enforce-iter-loop-reborrow: Whether to recommend using implicit into iter for reborrowed values.let mut vec = vec![1, 2, 3];let rmvec = &mut vec;for _ in rmvec.iter() {}for _ in rmvec.iter_mut() {}Use instead:
let mut vec = vec![1, 2, 3];let rmvec = &mut vec;for _ in &*rmvec {}for _ in &mut *rmvec {}(default:false)
Checks for usage ofwrite!() /writeln()! which can bereplaced with(e)print!() /(e)println!()
Using(e)println! is clearer and more concise
writeln!(&mut std::io::stderr(), "foo: {:?}", bar).unwrap();writeln!(&mut std::io::stdout(), "foo: {:?}", bar).unwrap();Use instead:
eprintln!("foo: {:?}", bar);println!("foo: {:?}", bar);Nothing. This lint has been deprecated
Vec::extend_from_slice is no longer faster thanVec::extend due to specialization.
Checks for occurrences where one vector gets extended instead of append
Usingappend instead ofextend is more concise and faster
let mut a = vec![1, 2, 3];let mut b = vec![4, 5, 6];a.extend(b.drain(..));Use instead:
let mut a = vec![1, 2, 3];let mut b = vec![4, 5, 6];a.append(&mut b);Checks for lifetimes in generics that are never usedanywhere else.
The additional lifetimes make the code look morecomplicated, while there is nothing out of the ordinary going on. Removingthem leads to more readable code.
// unnecessary lifetimesfn unused_lifetime<'a>(x: u8) { // ..}Use instead:
fn no_lifetime(x: u8) { // ...}Checks for type parameters in generics that are never used anywhere else.
Functions cannot infer the value of unused type parameters; therefore, calling themrequires using a turbofish, which serves no purpose but to satisfy the compiler.
fn unused_ty<T>(x: u8) { // ..}Use instead:
fn no_unused_ty(x: u8) { // ..}Checks for impls ofFrom<..> that containpanic!() orunwrap()
TryFrom should be used if there’s a possibility of failure.
struct Foo(i32);impl From<String> for Foo { fn from(s: String) -> Self { Foo(s.parse().unwrap()) }}Use instead:
struct Foo(i32);impl TryFrom<String> for Foo { type Error = (); fn try_from(s: String) -> Result<Self, Self::Error> { if let Ok(parsed) = s.parse() { Ok(Foo(parsed)) } else { Err(()) } }}Checks for immediate reassignment of fields initializedwith Default::default().
It’s more idiomatic to use thefunctional update syntax.
Assignments to patterns that are of tuple type are not linted.
let mut a: A = Default::default();a.i = 42;Use instead:
let a = A { i: 42, .. Default::default()};Checks for usage of scoped visibility modifiers, likepub(crate), on fields. Thesemake a field visible within a scope between public and private.
Scoped visibility modifiers cause a field to be accessible within some scope betweenpublic and private, potentially within an entire crate. This allows for fields to benon-private while upholding internal invariants, but can be a code smell. Scoped visibilityrequires checking a greater area, potentially an entire crate, to verify that an invariantis upheld, and global analysis requires a lot of effort.
pub mod public_module { struct MyStruct { pub(crate) first_field: bool, pub(super) second_field: bool }}Use instead:
pub mod public_module { struct MyStruct { first_field: bool, second_field: bool } impl MyStruct { pub(crate) fn get_first_field(&self) -> bool { self.first_field } pub(super) fn get_second_field(&self) -> bool { self.second_field } }}Checks forFileType::is_file().
When people testing a file type withFileType::is_filethey are testing whether a path is something they can get bytes from. Butis_file doesn’t cover special file types in unix-like systems, and doesn’t coversymlink in windows. Using!FileType::is_dir() is a better way to that intention.
let metadata = std::fs::metadata("foo.txt")?;let filetype = metadata.file_type();if filetype.is_file() { // read file}should be written as:
let metadata = std::fs::metadata("foo.txt")?;let filetype = metadata.file_type();if !filetype.is_dir() { // read file}Checks for usage ofbool::then inIterator::filter_map.
This can be written withfilter thenmap instead, which would reduce nesting andseparates the filtering from the transformation phase. This comes with no cost toperformance and is just cleaner.
Does not lintbool::then_some, as it eagerly evaluates its arguments rather than lazily.This can create differing behavior, so better safe than sorry.
_ = v.into_iter().filter_map(|i| (i % 2 == 0).then(|| really_expensive_fn(i)));Use instead:
_ = v.into_iter().filter(|i| i % 2 == 0).map(|i| really_expensive_fn(i));Checks for usage offilter_map(|x| x).
Readability, this can be written more concisely by usingflatten.
iter.filter_map(|x| x);Use instead:
iter.flatten();Checks for usage of_.filter_map(_).next().
Readability, this can be written more concisely as_.find_map(_).
(0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next();Can be written as
(0..3).find_map(|x| if x == 2 { Some(x) } else { None });msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage of_.filter(_).next().
Readability, this can be written more concisely as_.find(_).
vec.iter().filter(|x| **x == 0).next();Use instead:
vec.iter().find(|x| **x == 0);Checks for usage offlat_map(|x| x).
Readability, this can be written more concisely by usingflatten.
iter.flat_map(|x| x);Can be written as
iter.flatten();Checks for usage ofIterator::flat_map() wherefilter_map() could beused instead.
filter_map() is known to always produce 0 or 1 output items per input item,rather than however many the inner iterator type produces.Therefore, it maintains the upper bound inIterator::size_hint(),and communicates to the reader that the input items are not being expanded intomultiple output items without their having to notice that the mapping functionreturns anOption.
let nums: Vec<i32> = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect();Use instead:
let nums: Vec<i32> = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect();Checks for float arithmetic.
For some embedded systems or kernel development, itcan be useful to rule out floating-point numbers.
a + 1.0;Checks for (in-)equality comparisons on floating-pointvalues (apart from zero), except in functions called*eq* (which probablyimplement equality for a type involving floats).
Floating point calculations are usually imprecise, so asking if two values areexactlyequal is asking for trouble because arriving at the same logical result via differentroutes (e.g. calculation versus constant) may yield different values.
let a: f64 = 1000.1;let b: f64 = 0.2;let x = a + b;let y = 1000.3; // Expected value.// Actual value: 1000.3000000000001println!("{x}");let are_equal = x == y;println!("{are_equal}"); // falseThe correct way to compare floating point numbers is to define an allowed error margin. Thismay be challenging if there is no “natural” error margin to permit. Broadly speaking, thereare two cases:
For the scenario where you can define a meaningful absolute error margin, consider using:
let a: f64 = 1000.1;let b: f64 = 0.2;let x = a + b;let y = 1000.3; // Expected value.const ALLOWED_ERROR_VEHICLE_LENGTH_CM: f64 = 0.1;let within_tolerance = (x - y).abs() < ALLOWED_ERROR_VEHICLE_LENGTH_CM;println!("{within_tolerance}"); // trueNOTE: Do not usef64::EPSILON - while the error margin is often called “epsilon”, this isa different use of the term that is not suitable for floating point equality comparison.Indeed, for the example above usingf64::EPSILON as the allowed error would returnfalse.
For the scenario where no meaningful absolute error can be defined, refer tothe floating point guidefor a reference implementation of relative error based comparison of floating point values.MIN_NORMAL in the reference implementation is equivalent toMIN_POSITIVE in Rust.
Checks for (in-)equality comparisons on constant floating-pointvalues (apart from zero), except in functions called*eq* (which probablyimplement equality for a type involving floats).
Floating point calculations are usually imprecise, so asking if two values areexactlyequal is asking for trouble because arriving at the same logical result via differentroutes (e.g. calculation versus constant) may yield different values.
let a: f64 = 1000.1;let b: f64 = 0.2;let x = a + b;const Y: f64 = 1000.3; // Expected value.// Actual value: 1000.3000000000001println!("{x}");let are_equal = x == Y;println!("{are_equal}"); // falseThe correct way to compare floating point numbers is to define an allowed error margin. Thismay be challenging if there is no “natural” error margin to permit. Broadly speaking, thereare two cases:
For the scenario where you can define a meaningful absolute error margin, consider using:
let a: f64 = 1000.1;let b: f64 = 0.2;let x = a + b;const Y: f64 = 1000.3; // Expected value.const ALLOWED_ERROR_VEHICLE_LENGTH_CM: f64 = 0.1;let within_tolerance = (x - Y).abs() < ALLOWED_ERROR_VEHICLE_LENGTH_CM;println!("{within_tolerance}"); // trueNOTE: Do not usef64::EPSILON - while the error margin is often called “epsilon”, this isa different use of the term that is not suitable for floating point equality comparison.Indeed, for the example above usingf64::EPSILON as the allowed error would returnfalse.
For the scenario where no meaningful absolute error can be defined, refer tothe floating point guidefor a reference implementation of relative error based comparison of floating point values.MIN_NORMAL in the reference implementation is equivalent toMIN_POSITIVE in Rust.
Checks for statements of the form(a - b) < f32::EPSILON or(a - b) < f64::EPSILON. Note the missing.abs().
The code without.abs() is more likely to have a bug.
If the user can ensure that b is larger than a, the.abs() istechnically unnecessary. However, it will make the code more robust and doesn’t have anylarge performance implications. If the abs call was deliberately left out for performancereasons, it is probably better to state this explicitly in the code, which then can be donewith an allow.
pub fn is_roughly_equal(a: f32, b: f32) -> bool { (a - b) < f32::EPSILON}Use instead:
pub fn is_roughly_equal(a: f32, b: f32) -> bool { (a - b).abs() < f32::EPSILON}Checks for excessive use ofbools in function definitions.
Calls to such functionsare confusing and error prone, because it’shard to remember argument order and you haveno type system support to back you up. Usingtwo-variant enums instead of bools often makesAPI easier to use.
fn f(is_round: bool, is_hot: bool) { ... }Use instead:
enum Shape { Round, Spiky,}enum Temperature { Hot, IceCold,}fn f(shape: Shape, temperature: Temperature) { ... }max-fn-params-bools: The maximum number of bool parameters a function can have
(default:3)
Checks for casts of function pointers to something other thanusize.
Casting a function pointer to anything other thanusize/isize isnot portable across architectures. If the target type is too small theaddress would be truncated, and target types larger thanusize areunnecessary.
Casting toisize also doesn’t make sense, since addresses are neversigned.
fn fun() -> i32 { 1 }let _ = fun as i64;Use instead:
let _ = fun as usize;Checks for casts of a function pointer to any integer type.
Casting a function pointer to an integer can have surprising results and can occuraccidentally if parentheses are omitted from a function call. If you aren’t doing anythinglow-level with function pointers then you can opt out of casting functions to integers inorder to avoid mistakes. Alternatively, you can use this lint to audit all uses of functionpointer casts in your code.
// fn1 is cast as `usize`fn fn1() -> u16 { 1};let _ = fn1 as usize;Use instead:
// maybe you intended to call the function?fn fn2() -> u16 { 1};let _ = fn2() as usize;// or// maybe you intended to cast it to a function type?fn fn3() -> u16 { 1}let _ = fn3 as fn() -> u16;Checks for casts of a function pointer to a numeric type not wide enough tostore an address.
Such a cast discards some bits of the function’s address. If this is intended, it would be moreclearly expressed by casting tousize first, then casting theusize to the intended type (witha comment) to perform the truncation.
fn fn1() -> i16 { 1};let _ = fn1 as i32;Use instead:
// Cast to usize first, then comment with the reason for the truncationfn fn1() -> i16 { 1};let fn_ptr = fn1 as usize;let fn_ptr_truncated = fn_ptr as i32;Checks for iterating a map (HashMap orBTreeMap) andignoring either the keys or values.
Readability. There arekeys andvalues methods thatcan be used to express that don’t need the values or keys.
for (k, _) in &map { ..}could be replaced by
for k in map.keys() { ..}Checks for calls tostd::mem::forget with a value that does not implementDrop.
Callingstd::mem::forget is no different than dropping such a type. A different value mayhave been intended.
struct Foo;let x = Foo;std::mem::forget(x);Checks for usage of.map(|_| format!(..)).collect::<String>().
This allocates a new string for every element in the iterator.This can be done more efficiently by creating theString once and appending to it inIterator::fold,using either thewrite! macro which supports exactly the same syntax as theformat! macro,or concatenating with+ in case the iterator yields&str/String.
Note also thatwrite!-ing into aString can never fail, despite the return type ofwrite! beingstd::fmt::Result,so it can be safely ignored or unwrapped.
fn hex_encode(bytes: &[u8]) -> String { bytes.iter().map(|b| format!("{b:02X}")).collect()}Use instead:
use std::fmt::Write;fn hex_encode(bytes: &[u8]) -> String { bytes.iter().fold(String::new(), |mut output, b| { let _ = write!(output, "{b:02X}"); output })}Detectsformat! within the arguments of another macro that doesformatting such asformat! itself,write! orprintln!. Suggestsinlining theformat! call.
The recommended code is both shorter and avoids a temporary allocation.
println!("error: {}", format!("something failed at {}", Location::caller()));Use instead:
println!("error: something failed at {}", Location::caller());Detects cases where the result of aformat! call isappended to an existingString.
Introduces an extra, avoidable heap allocation.
format! returns aString butwrite! returns aResult.Thus you are forced to ignore theErr variant to achieve the same API.
While usingwrite! in the suggested way should never fail, this isn’t necessarily clear to the programmer.
let mut s = String::new();s += &format!("0x{:X}", 1024);s.push_str(&format!("0x{:X}", 1024));Use instead:
use std::fmt::Write as _; // import without risk of name clashinglet mut s = String::new();let _ = write!(s, "0x{:X}", 1024);Checks for outer doc comments written with 4 forward slashes (////).
This is (probably) a typo, and results in it not being a doc comment; just a regularcomment.
//// My amazing data structurepub struct Foo { // ...}Use instead:
/// My amazing data structurepub struct Foo { // ...}Checks forfrom_iter() function calls on types that implement theFromIteratortrait.
If it’s needed to create a collection from the contents of an iterator, theIterator::collect(_)method is preferred. However, when it’s needed to specify the container type,Vec::from_iter(_) can be more readable than using a turbofish (e.g._.collect::<Vec<_>>()). SeeFromIterator documentation
let five_fives = std::iter::repeat(5).take(5);let v = Vec::from_iter(five_fives);assert_eq!(v, vec![5, 5, 5, 5, 5]);Use instead:
let five_fives = std::iter::repeat(5).take(5);let v: Vec<i32> = five_fives.collect();assert_eq!(v, vec![5, 5, 5, 5, 5]);but prefer to use
let numbers: Vec<i32> = FromIterator::from_iter(1..=5);instead of
let numbers = (1..=5).collect::<Vec<_>>();Searches for implementations of theInto<..> trait and suggests to implementFrom<..> instead.
According the std docs implementingFrom<..> is preferred since it gives youInto<..> for free where the reverse isn’t true.
struct StringWrapper(String);impl Into<StringWrapper> for String { fn into(self) -> StringWrapper { StringWrapper(self) }}Use instead:
struct StringWrapper(String);impl From<String> for StringWrapper { fn from(s: String) -> StringWrapper { StringWrapper(s) }}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks if we’re passing ac_void raw pointer to{Box,Rc,Arc,Weak}::from_raw(_)
When dealing withc_void raw pointers in FFI, it is easy to run into the pitfall of callingfrom_raw with thec_void pointer.The type signature ofBox::from_raw isfn from_raw(raw: *mut T) -> Box<T>, so if you pass a*mut c_void you will get aBox<c_void> (and similarly forRc,Arc andWeak).For this to be safe,c_void would need to have the same memory layout as the original type, which is often not the case.
let ptr = Box::into_raw(Box::new(42usize)) as *mut c_void;let _ = unsafe { Box::from_raw(ptr) };Use instead:
let _ = unsafe { Box::from_raw(ptr as *mut usize) };Checks for function invocations of the formprimitive::from_str_radix(s, 10)
This specific common use case can be rewritten ass.parse::<primitive>()(and in most cases, the turbofish can be removed), which reduces code lengthand complexity.
This lint may suggest using(&<expression>).parse() instead of<expression>.parse()directly in some cases, which is correct but adds unnecessary complexity to the code.
let input: &str = get_input();let num = u16::from_str_radix(input, 10)?;Use instead:
let input: &str = get_input();let num: u16 = input.parse()?;This lint requires Future implementations returned fromfunctions and methods to implement theSend marker trait,ignoring type parameters.
If a function is generic and its Future conditionally implementsSendbased on a generic parameter then it is consideredSend and no warning is emitted.
This can be used by library authors (public and internal) to ensuretheir functions are compatible with both multi-threaded runtimes that requireSend futures,as well as single-threaded runtimes where callers may choose!Send typesfor generic parameters.
A Future implementation captures some state that itneeds to eventually produce its final value. When targeting a multithreadedexecutor (which is the norm on non-embedded devices) this means that thisstate may need to be transported to other threads, in other words thewhole Future needs to implement theSend marker trait. If it does not,then the resulting Future cannot be submitted to a thread pool in theend user’s code.
Especially for generic functions it can be confusing to leave thediscovery of this problem to the end user: the reported error locationwill be far from its cause and can in many cases not even be fixed withoutmodifying the library where the offending Future implementation isproduced.
async fn not_send(bytes: std::rc::Rc<[u8]>) {}Use instead:
async fn is_send(bytes: std::sync::Arc<[u8]>) {}Checks for usage ofx.get(0) instead ofx.first() orx.front().
Usingx.first() forVecs and slices orx.front()forVecDeques is easier to read and has the same result.
let x = vec![2, 3, 5];let first_element = x.get(0);Use instead:
let x = vec![2, 3, 5];let first_element = x.first();Checks for usage ofx.get(x.len() - 1) instead ofx.last().
Usingx.last() is easier to read and has the sameresult.
Note that usingx[x.len() - 1] is semantically different fromx.last(). Indexing into the array will panic on out-of-boundsaccesses, whilex.get() andx.last() will returnNone.
There is another lint (get_unwrap) that covers the case of usingx.get(index).unwrap() instead ofx[index].
let x = vec![2, 3, 5];let last_element = x.get(x.len() - 1);Use instead:
let x = vec![2, 3, 5];let last_element = x.last();Checks for usage of.get().unwrap() (or.get_mut().unwrap) on a standard library type which implementsIndex
Using the Index trait ([]) is more clear and moreconcise.
Not a replacement for error handling: Using either.unwrap() or the Index trait ([]) carries the risk of causing apanicif the value being accessed isNone. If the use of.get().unwrap() is atemporary placeholder for dealing with theOption type, then this doesnot mitigate the need for error handling. If there is a chance that.get()will beNone in your program, then it is advisable that theNone caseis handled in a future refactor instead of using.unwrap() or the Indextrait.
let mut some_vec = vec![0, 1, 2, 3];let last = some_vec.get(3).unwrap();*some_vec.get_mut(0).unwrap() = 1;The correct use would be:
let mut some_vec = vec![0, 1, 2, 3];let last = some_vec[3];some_vec[0] = 1;Checks for the usage of theto_ne_bytes method and/or the functionfrom_ne_bytes.
To ensure use of explicitly chosen endianness rather than the target’s endianness,such as when implementing network protocols or file formats rather than FFI.
let _x = 2i32.to_ne_bytes();let _y = 2i64.to_ne_bytes();Checks for identity operations, e.g.,x + 0.
This code can be removed without changing themeaning. So it just obscures what’s going on. Delete it mercilessly.
x / 1 + 0 * 1 - 0 | 0;Checks forMutex::lock calls inif let expressionwith lock calls in any of the else blocks.
This lint is effectively disabled starting inEdition 2024 asif let ... else scoping was reworkedsuch that this is no longer an issue. SeeProposal: stabilize if_let_rescope for Edition 2024
The Mutex lock remains held for the wholeif let ... else block and deadlocks.
if let Ok(thing) = mutex.lock() { do_thing();} else { mutex.lock();}Should be written
let locked = mutex.lock();if let Ok(thing) = locked { do_thing(thing);} else { use_locked(locked);}Checks for usage of! or!= in an if condition with anelse branch.
Negations reduce the readability of statements.
if !v.is_empty() { a()} else { b()}Could be written:
if v.is_empty() { b()} else { a()}Checks forif/else with the same body as thethen partand theelse part.
This is probably a copy & paste error.
let foo = if … { 42} else { 42};Checks for if-else that could be written using eitherbool::then orbool::then_some.
Looks a little redundant. Usingbool::then is more concise and incurs no loss of clarity.For simple calculations and known values, usebool::then_some, which is eagerly evaluatedin comparison tobool::then.
let a = if v.is_empty() { println!("true!"); Some(42)} else { None};Could be written:
let a = v.is_empty().then(|| { println!("true!"); 42});msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for consecutiveifs with the same condition.
This is probably a copy & paste error.
if a == b { …} else if a == b { …}Note that this lint ignores all conditions with a function call as it couldhave side effects:
if foo() { …} else if foo() { // not linted …}ignore-interior-mutability: A list of paths to types that should be treated as if they do not contain interior mutability
(default:["bytes::Bytes"])
Checks for ignored tests without messages.
The reason for ignoring the test may not be obvious.
#[test]#[ignore]fn test() {}Use instead:
#[test]#[ignore = "Some good reason"]fn test() {}Checks for usage of_ in patterns of type().
Matching with() explicitly instead of_ outlinesthe fact that the pattern contains no data. Also itwould detect a type change that_ would ignore.
match std::fs::create_dir("tmp-work-dir") { Ok(_) => println!("Working directory created"), Err(s) => eprintln!("Could not create directory: {s}"),}Use instead:
match std::fs::create_dir("tmp-work-dir") { Ok(()) => println!("Working directory created"), Err(s) => eprintln!("Could not create directory: {s}"),}This lint is concerned with the semantics ofBorrow andHash for atype that implements all three ofHash,Borrow<str> andBorrow<[u8]>as it is impossible to satisfy the semantics of Borrow andHash forbothBorrow<str> andBorrow<[u8]>.
When providing implementations forBorrow<T>, one should consider whether the differentimplementations should act as facets or representations of the underlying type. Generic codetypically usesBorrow<T> when it relies on the identical behavior of these additional traitimplementations. These traits will likely appear as additional trait bounds.
In particularEq,Ord andHash must be equivalent for borrowed and owned values:x.borrow() == y.borrow() should give the same result asx == y.It follows then that the following equivalence must hold:hash(x) == hash((x as Borrow<[u8]>).borrow()) == hash((x as Borrow<str>).borrow())
Unfortunately it doesn’t hold ashash("abc") != hash("abc".as_bytes()).This happens because theHash impl for str passes an additional0xFF byte tothe hasher to avoid collisions. For example, given the tuples("a", "bc"), and("ab", "c"),the two tuples would have the same hash value if the0xFF byte was not added.
use std::borrow::Borrow;use std::hash::{Hash, Hasher};struct ExampleType { data: String}impl Hash for ExampleType { fn hash<H: Hasher>(&self, state: &mut H) { self.data.hash(state); }}impl Borrow<str> for ExampleType { fn borrow(&self) -> &str { &self.data }}impl Borrow<[u8]> for ExampleType { fn borrow(&self) -> &[u8] { self.data.as_bytes() }}As a consequence, hashing a&ExampleType and hashing the result of the twoborrows will result in different values.
Lints whenimpl Trait is being used in a function’s parameters.
Turbofish syntax (::<>) cannot be used to specify the type of animpl Trait parameter,makingimpl Trait less powerful. Readability may also be a factor.
trait MyTrait {}fn foo(a: impl MyTrait) {// [...]}Use instead:
trait MyTrait {}fn foo<T: MyTrait>(a: T) {// [...]}Checks for the usage of_.to_owned(),vec.to_vec(), or similar when calling_.clone() would be clearer.
These methods do the same thing as_.clone() but may be confusing asto why we are callingto_vec on something that is already aVec or callingto_owned on something that is already owned.
let a = vec![1, 2, 3];let b = a.to_vec();let c = a.to_owned();Use instead:
let a = vec![1, 2, 3];let b = a.clone();let c = a.clone();Checks for publicimpl orfn missing generalizationover different hashers and implicitly defaulting to the default hashingalgorithm (SipHash).
HashMap orHashSet with custom hashers cannot beused with them.
Suggestions for replacing constructors can containfalse-positives. Also applying suggestions can require modification of otherpieces of code, possibly including external crates.
impl<K: Hash + Eq, V> Serialize for HashMap<K, V> { }pub fn foo(map: &mut HashMap<i32, i32>) { }could be rewritten as
impl<K: Hash + Eq, V, S: BuildHasher> Serialize for HashMap<K, V, S> { }pub fn foo<S: BuildHasher>(map: &mut HashMap<i32, i32, S>) { }Checks for missing return statements at the end of a block.
Omitting the return keyword whenever possible is idiomatic Rust code, but:
return.;.return keyword makes it easier to find thecorresponding statements.fn foo(x: usize) -> usize { x}add return
fn foo(x: usize) -> usize { return x;}Checks for implicit saturating addition.
The built-in function is more readable and may be faster.
let mut u:u32 = 7000;if u != u32::MAX { u += 1;}Use instead:
let mut u:u32 = 7000;u = u.saturating_add(1);Checks for implicit saturating subtraction.
Simplicity and readability. Instead we can easily use an builtin function.
let mut i: u32 = end - start;if i != 0 { i -= 1;}Use instead:
let mut i: u32 = end - start;i = i.saturating_sub(1);Looks for bounds inimpl Trait in return position that are implied by other bounds.This can happen when a trait is specified that another trait already has as a supertrait(e.g.fn() -> impl Deref + DerefMut<Target = i32> has an unnecessaryDeref bound,becauseDeref is a supertrait ofDerefMut)
Specifying more bounds than necessary adds needless complexity for the reader.
This lint does not check for implied bounds transitively. Meaning thatit doesn’t check for implied bounds from supertraits of supertraits(e.g.trait A {} trait B: A {} trait C: B {}, then having anfn() -> impl A + C)
fn f() -> impl Deref<Target = i32> + DerefMut<Target = i32> {// ^^^^^^^^^^^^^^^^^^^ unnecessary bound, already implied by the `DerefMut` trait bound Box::new(123)}Use instead:
fn f() -> impl DerefMut<Target = i32> { Box::new(123)}Checks for double comparisons that can never succeed
The whole expression can be replaced byfalse,which is probably not the programmer’s intention
if status_code <= 400 && status_code > 500 {}Looks for floating-point expressions thatcan be expressed using built-in methods to improve accuracyat the cost of performance.
Negatively impacts accuracy.
let a = 3f32;let _ = a.powf(1.0 / 3.0);let _ = (1.0 + a).ln();let _ = a.exp() - 1.0;Use instead:
let a = 3f32;let _ = a.cbrt();let _ = a.ln_1p();let _ = a.exp_m1();This lint checks that no function newer than the defined MSRV (minimumsupported rust version) is used in the crate.
It would prevent the crate to be actually used with the specified MSRV.
// MSRV of 1.3.0use std::thread::sleep;use std::time::Duration;// Sleep was defined in `1.4.0`.sleep(Duration::new(1, 0));To fix this problem, either increase your MSRV or use another itemavailable in your current MSRV.
You can also locally change the MSRV that should be checked by Clippy,for example if a feature in your crate (e.g.,modern_compiler) shouldallow you to use an item:
//! This crate has a MSRV of 1.3.0, but we also have an optional feature//! `sleep_well` which requires at least Rust 1.4.0.// When the `sleep_well` feature is set, do not warn for functions available// in Rust 1.4.0 and below.#![cfg_attr(feature = "sleep_well", clippy::msrv = "1.4.0")]use std::time::Duration;#[cfg(feature = "sleep_well")]fn sleep_for_some_time() { std::thread::sleep(Duration::new(1, 0)); // Will not trigger the lint}You can also increase the MSRV in tests, by using:
// Use a much higher MSRV for tests while keeping the main one low#![cfg_attr(test, clippy::msrv = "1.85.0")]#[test]fn my_test() { // The tests can use items introduced in Rust 1.85.0 and lower // without triggering the `incompatible_msrv` lint.}check-incompatible-msrv-in-tests: Whether to check MSRV compatibility in#[test] and#[cfg(test)] code.
(default:false)
Warns if an integral or floating-point constant isgrouped inconsistently with underscores.
Readers may incorrectly interpret inconsistentlygrouped digits.
618_64_9189_73_511Use instead:
61_864_918_973_511Checks for struct constructors where the order of the fieldinit in the constructor is inconsistent with the order in thestruct definition.
Since the order of fields in a constructor doesn’t affect theresulted instance as the below example indicates,
#[derive(Debug, PartialEq, Eq)]struct Foo { x: i32, y: i32,}let x = 1;let y = 2;// This assertion never fails:assert_eq!(Foo { x, y }, Foo { y, x });inconsistent order can be confusing and decreases readability and consistency.
struct Foo { x: i32, y: i32,}let x = 1;let y = 2;Foo { y, x };Use instead:
Foo { x, y };check-inconsistent-struct-field-initializers: Whether to suggest reordering constructor fields when initializers are present.Warnings produced by this configuration aren’t necessarily fixed by just reordering the fields. Even if thesuggested code would compile, it can change semantics if the initializer expressions have side effects. Thefollowing examplefrom rust-clippy#11846 shows how the suggestion can run into borrow check errors:
struct MyStruct { vector: Vec<u32>, length: usize}fn main() { let vector = vec![1,2,3]; MyStruct { length: vector.len(), vector};}(default:false)
The lint checks for slice bindings in patterns that are only used toaccess individual slice values.
Accessing slice values using indices can lead to panics. Using refutablepatterns can avoid these. Binding to individual values also improves thereadability as they can be named.
This lint currently only checks for immutable access insideif letpatterns.
let slice: Option<&[u32]> = Some(&[1, 2, 3]);if let Some(slice) = slice { println!("{}", slice[0]);}Use instead:
let slice: Option<&[u32]> = Some(&[1, 2, 3]);if let Some(&[first, ..]) = slice { println!("{}", first);}max-suggested-slice-pattern-length: When Clippy suggests using a slice pattern, this is the maximum number of elements allowed inthe slice pattern that is suggested. If more elements are necessary, the lint is suppressed.For example,[_, _, _, e, ..] is a slice pattern with 4 elements.
(default:3)
msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage of indexing or slicing that may panic at runtime.
This lint does not report on indexing or slicing operationsthat always panic, clippy’sout_of_bound_indexing alreadyhandles those cases.
To avoid implicit panics from indexing and slicing.
There are “checked” alternatives which do not panic, and can be used withunwrap() to makean explicit panic when it is desired.
This lint does not check for the usage of indexing or slicing on strings. These are coveredby the more specificstring_slice lint.
// Vectorlet x = vec![0, 1, 2, 3];x[2];x[100];&x[2..100];// Arraylet y = [0, 1, 2, 3];let i = 10; // Could be a runtime valuelet j = 20;&y[i..j];Use instead:
x.get(2);x.get(100);x.get(2..100);let i = 10;let j = 20;y.get(i..j);allow-indexing-slicing-in-tests: Whetherindexing_slicing should be allowed in test functions or#[cfg(test)]
(default:false)
suppress-restriction-lint-in-const: Whether to suppress a restriction lint in constant code. In samecases the restructured operation might not be unavoidable, as thesuggested counterparts are unavailable in constant code. Thisconfiguration will cause restriction lints to trigger evenif no suggestion can be made.
(default:false)
Checks for bit masks in comparisons which can be removedwithout changing the outcome. The basic structure can be seen in thefollowing table:
| Comparison | Bit Op | Example | equals |
|---|---|---|---|
> /<= | | /^ | x | 2 > 3 | x > 3 |
< />= | | /^ | x ^ 1 < 4 | x < 4 |
Not equally evil asbad_bit_mask,but still a bit misleading, because the bit mask is ineffective.
False negatives: This lint will only match instanceswhere we have figured out the math (which is for a power-of-two comparedvalue). This means things likex | 1 >= 7 (which would be better writtenasx >= 6) will not be reported (but bit masks like this are fairlyuncommon).
if (x | 1 > 3) { }Use instead:
if (x >= 2) { }Checks if both.write(true) and.append(true) methods are calledon a sameOpenOptions.
.append(true) already enableswrite(true), making this onesuperfluous.
let _ = OpenOptions::new() .write(true) .append(true) .create(true) .open("file.json");Use instead:
let _ = OpenOptions::new() .append(true) .create(true) .open("file.json");Checks for usage of.to_string() on an&&T whereT implementsToString directly (like&&str or&&String).
In versions of the compiler before Rust 1.82.0, this bypasses the specializedimplementation ofToString and instead goes through the more expensive stringformatting facilities.
// Generic implementation for `T: Display` is used (slow)["foo", "bar"].iter().map(|s| s.to_string());// OK, the specialized impl is used["foo", "bar"].iter().map(|&s| s.to_string());msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for matches being used to destructure a single-variant enumor tuple struct where alet will suffice.
Just readability –let doesn’t nest, whereas amatch does.
enum Wrapper { Data(i32),}let wrapper = Wrapper::Data(42);let data = match wrapper { Wrapper::Data(i) => i,};The correct use would be:
enum Wrapper { Data(i32),}let wrapper = Wrapper::Data(42);let Wrapper::Data(data) = wrapper;Finds manual impls ofTryFrom with infallible error types.
Infallible conversions should be implemented viaFrom with the blanket conversion.
use std::convert::Infallible;struct MyStruct(i16);impl TryFrom<i16> for MyStruct { type Error = Infallible; fn try_from(other: i16) -> Result<Self, Infallible> { Ok(Self(other.into())) }}Use instead:
struct MyStruct(i16);impl From<i16> for MyStruct { fn from(other: i16) -> Self { Self(other) }}Checks for iteration that is guaranteed to be infinite.
While there may be places where this is acceptable(e.g., in event streams), in most cases this is simply an error.
use std::iter;iter::repeat(1_u8).collect::<Vec<_>>();Checks for infinite loops in a function where the return type is not!and lint accordingly.
Making the return type! serves as documentation that the function does not return.If the function is not intended to loop infinitely, then this lint may detect a bug.
fn run_forever() { loop { // do something }}If infinite loops are as intended:
fn run_forever() -> ! { loop { // do something }}Otherwise add abreak orreturn condition:
fn run_forever() { loop { // do something if condition { break; } }}Checks for the definition of inherent methods with a signature ofto_string(&self) -> String.
This method is also implicitly defined if a type implements theDisplay trait. As the functionality ofDisplay is much more versatile, it should be preferred.
pub struct A;impl A { pub fn to_string(&self) -> String { "I am A".to_string() }}Use instead:
use std::fmt;pub struct A;impl fmt::Display for A { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "I am A") }}Checks for the definition of inherent methods with a signature ofto_string(&self) -> String and if the type implementing this method also implements theDisplay trait.
This method is also implicitly defined if a type implements theDisplay trait. The less versatile inherent method will then shadow the implementation introduced byDisplay.
use std::fmt;pub struct A;impl A { pub fn to_string(&self) -> String { "I am A".to_string() }}impl fmt::Display for A { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "I am A, too") }}Use instead:
use std::fmt;pub struct A;impl fmt::Display for A { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "I am A") }}Checks for tuple structs initialized with field syntax.It will however not lint if a base initializer is present.The lint will also ignore code in macros.
This may be confusing to the uninitiated and adds nobenefit as opposed to tuple initializers
struct TupleStruct(u8, u16);let _ = TupleStruct { 0: 1, 1: 23,};// should be written aslet base = TupleStruct(1, 23);// This is OK howeverlet _ = TupleStruct { 0: 42, ..base };Checks for items annotated with#[inline(always)],unless the annotated function is empty or simply panics.
While there are valid uses of this annotation (and onceyou know when to use it, by all meansallow this lint), it’s a commonnewbie-mistake to pepper one’s code with it.
As a rule of thumb, before slapping#[inline(always)] on a function,measure if that additional function call really affects your runtime profilesufficiently to make up for the increase in compile time.
False positives, big time. This lint is meant to bedeactivated by everyone doing serious performance work. This means havingdone the measurement.
#[inline(always)]fn not_quite_hot_code(..) { ... }Checks for usage of AT&T x86 assembly syntax.
To enforce consistent use of Intel x86 assembly syntax.
asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax));Use instead:
asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr);Checks for usage of Intel x86 assembly syntax.
To enforce consistent use of AT&T x86 assembly syntax.
asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr);Use instead:
asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax));Checks for#[inline] on trait methods without bodies
Only implementations of trait methods may be inlined.The inline attribute is ignored for trait methods without bodies.
trait Animal { #[inline] fn name(&self) -> &'static str;}Checks for usage ofinspect().for_each().
It is the same as performing the computationinsideinspect at the beginning of the closure infor_each.
[1,2,3,4,5].iter().inspect(|&x| println!("inspect the number: {}", x)).for_each(|&x| { assert!(x >= 0);});Can be written as
[1,2,3,4,5].iter().for_each(|&x| { println!("inspect the number: {}", x); assert!(x >= 0);});Checks for usage ofx >= y + 1 orx - 1 >= y (and<=) in a block
Readability – better to use> y instead of>= y + 1.
if x >= y + 1 {}Use instead:
if x > y {}Checks for division of integers
When outside of some very specific algorithms,integer division is very often a mistake because it discards theremainder.
let x = 3 / 2;println!("{}", x);Use instead:
let x = 3f32 / 2f32;println!("{}", x);Checks for the usage of division (/) and remainder (%) operationswhen performed on any integer types using the defaultDiv andRem trait implementations.
In cryptographic contexts, division can result in timing sidechannel vulnerabilities,and needs to be replaced with constant-time code instead (e.g. Barrett reduction).
let my_div = 10 / 2;Use instead:
let my_div = 10 >> 1;Checks forinto_iter calls on references which should be replaced byiteroriter_mut.
Readability. Callinginto_iter on a reference will not move out itscontent into the resulting iterator, which is confusing. It is better just calliter oriter_mut directly.
(&vec).into_iter();Use instead:
(&vec).iter();This is the opposite of theiter_without_into_iter lint.It looks forIntoIterator for (&|&mut) Type implementations without an inherentiter oriter_mut methodon the type or on any of the types in itsDeref chain.
It’s not bad, but having them is idiomatic and allows the type to be used in iterator chainsby just calling.iter(), instead of the more awkward<&Type>::into_iter or(&val).into_iter() syntaxin case of ambiguity with anotherIntoIterator impl.
This lint focuses on providing an idiomatic API. Therefore, it will onlylint on types which are accessible outside of the crate. For internal types,these methods can be added on demand if they are actually needed. Otherwise,it would trigger thedead_code lint for the unused method.
struct MySlice<'a>(&'a [u8]);impl<'a> IntoIterator for &MySlice<'a> { type Item = &'a u8; type IntoIter = std::slice::Iter<'a, u8>; fn into_iter(self) -> Self::IntoIter { self.0.iter() }}Use instead:
struct MySlice<'a>(&'a [u8]);impl<'a> MySlice<'a> { pub fn iter(&self) -> std::slice::Iter<'a, u8> { self.into_iter() }}impl<'a> IntoIterator for &MySlice<'a> { type Item = &'a u8; type IntoIter = std::slice::Iter<'a, u8>; fn into_iter(self) -> Self::IntoIter { self.0.iter() }}Checksregex creation(withRegex::new,RegexBuilder::new, orRegexSet::new) for correctregex syntax.
This will lead to a runtime panic.
Regex::new("(")Use instead:
Regex::new("\(")Checks for comparisons where the relation is always eithertrue or false, but where one side has been upcast so that the comparison isnecessary. Only integer types are checked.
An expression likelet x : u8 = ...; (x as u32) > 300will mistakenly imply that it is possible forx to be outside the range ofu8.
let x: u8 = 1;(x as u32) > 300;Checks for comparisons between integers, followed by subtracting the greater value from thelower one.
This could result in an underflow and is most likely not what the user wants. If this wasintended to be a saturated subtraction, consider using thesaturating_sub method directly.
let a = 12u32;let b = 13u32;let result = if a > b { b - a } else { 0 };Use instead:
let a = 12u32;let b = 13u32;let result = a.saturating_sub(b);Checks for invisible Unicode characters in the code.
Having an invisible character in the code makes for allsorts of April fools, but otherwise is very much frowned upon.
You don’t see it, but there may be a zero-width space or soft hyphensomewhere in this text.
This lint warns on callingio::Error::new(..) with a kind ofio::ErrorKind::Other.
Since Rust 1.74, there’s theio::Error::other(_) shortcut.
use std::io;let _ = io::Error::new(io::ErrorKind::Other, "bad".to_string());Use instead:
let _ = std::io::Error::other("bad".to_string());msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for IP addresses that could be replaced with predefined constants such asIpv4Addr::new(127, 0, 0, 1) instead of using the appropriate constants.
Using specific IP addresses like127.0.0.1 or::1 is less clear and less maintainable than using thepredefined constantsIpv4Addr::LOCALHOST orIpv6Addr::LOCALHOST. These constants improve codereadability, make the intent explicit, and are less error-prone.
use std::net::{Ipv4Addr, Ipv6Addr};// IPv4 loopbacklet addr_v4 = Ipv4Addr::new(127, 0, 0, 1);// IPv6 loopbacklet addr_v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);Use instead:
use std::net::{Ipv4Addr, Ipv6Addr};// IPv4 loopbacklet addr_v4 = Ipv4Addr::LOCALHOST;// IPv6 loopbacklet addr_v6 = Ipv6Addr::LOCALHOST;Finds usages ofchar::is_digit thatcan be replaced withis_ascii_digit oris_ascii_hexdigit.
is_digit(..) is slower and requires specifying the radix.
let c: char = '6';c.is_digit(10);c.is_digit(16);Use instead:
let c: char = '6';c.is_ascii_digit();c.is_ascii_hexdigit();Checks for items declared after some statement in a block.
Items live for the entire scope they are declaredin. But statements are processed in order. This might cause confusion asit’s hard to figure out which item is meant in a statement.
fn foo() { println!("cake");}fn main() { foo(); // prints "foo" fn foo() { println!("foo"); } foo(); // prints "foo"}Use instead:
fn foo() { println!("cake");}fn main() { fn foo() { println!("foo"); } foo(); // prints "foo" foo(); // prints "foo"}Triggers if an item is declared after the testing module marked with#[cfg(test)].
Having items declared after the testing module is confusing and may lead to bad test coverage.
#[cfg(test)]mod tests { // [...]}fn my_function() { // [...]}Use instead:
fn my_function() { // [...]}#[cfg(test)]mod tests { // [...]}Checks for the use of.cloned().collect() on slice tocreate aVec.
.to_vec() is clearer
let s = [1, 2, 3, 4, 5];let s2: Vec<isize> = s[..].iter().cloned().collect();The better use would be:
let s = [1, 2, 3, 4, 5];let s2: Vec<isize> = s.to_vec();Checks for the use of.iter().count().
.len() is more efficient and morereadable.
let some_vec = vec![0, 1, 2, 3];some_vec.iter().count();&some_vec[..].iter().count();Use instead:
let some_vec = vec![0, 1, 2, 3];some_vec.len();&some_vec[..].len();Checks for usage of.filter(Result::is_ok) that may be replaced with a.flatten() call.This lint will require additional changes to the follow-up calls as it affects the type.
This pattern is often followed by manual unwrapping ofResult. The simplificationresults in more readable and succinct code without the need for manual unwrapping.
vec![Ok::<i32, String>(1)].into_iter().filter(Result::is_ok);Use instead:
vec![Ok::<i32, String>(1)].into_iter().flatten();Checks for usage of.filter(Option::is_some) that may be replaced with a.flatten() call.This lint will require additional changes to the follow-up calls as it affects the type.
This pattern is often followed by manual unwrapping of theOption. The simplificationresults in more readable and succinct code without the need for manual unwrapping.
vec![Some(1)].into_iter().filter(Option::is_some);Use instead:
vec![Some(1)].into_iter().flatten();Checks for iterating a map (HashMap orBTreeMap) andignoring either the keys or values.
Readability. There arekeys andvalues methods thatcan be used to express that we only need the keys or the values.
let map: HashMap<u32, u32> = HashMap::new();let values = map.iter().map(|(_, value)| value).collect::<Vec<_>>();Use instead:
let map: HashMap<u32, u32> = HashMap::new();let values = map.values().collect::<Vec<_>>();msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for loops onx.next().
next() returns eitherSome(value) if there was avalue, orNone otherwise. The insidious thing is thatOption<_>implementsIntoIterator, so that possibly one value will be iterated,leading to some hard to find bugs. No one will want to write such codeexcept to win an Underhanded RustContest.
for x in y.next() { ..}Checks for usage ofiter().next() on a Slice or an Array
These can be shortened into.get()
a[2..].iter().next();b.iter().next();should be written as:
a.get(2);b.get(0);Detects methods namediter oriter_mut that do not have a return type that implementsIterator.
Methods namediter oriter_mut conventionally return anIterator.
// `String` does not implement `Iterator`struct Data {}impl Data { fn iter(&self) -> String { todo!() }}Use instead:
use std::str::Chars;struct Data {}impl Data { fn iter(&self) -> Chars<'static> { todo!() }}Checks for usage of.iter().nth()/.iter_mut().nth() on standard library types that haveequivalent.get()/.get_mut() methods.
.get() and.get_mut() are equivalent but more concise.
let some_vec = vec![0, 1, 2, 3];let bad_vec = some_vec.iter().nth(3);let bad_slice = &some_vec[..].iter().nth(3);The correct use would be:
let some_vec = vec![0, 1, 2, 3];let bad_vec = some_vec.get(3);let bad_slice = &some_vec[..].get(3);Checks for the use ofiter.nth(0).
iter.next() is equivalent toiter.nth(0), as they both consume the next element,but is more readable.
let x = s.iter().nth(0);Use instead:
let x = s.iter().next();Checks for calls toiter,iter_mut orinto_iter on empty collections
It is simpler to use the empty function from the standard library:
use std::{slice, option};let a: slice::Iter<i32> = [].iter();let f: option::IntoIter<i32> = None.into_iter();Use instead:
use std::iter;let a: iter::Empty<i32> = iter::empty();let b: iter::Empty<i32> = iter::empty();The type of the resulting iterator might become incompatible with its usage
Checks for calls toiter,iter_mut orinto_iter on collections containing a single item
It is simpler to use the once function from the standard library:
let a = [123].iter();let b = Some(123).into_iter();Use instead:
use std::iter;let a = iter::once(&123);let b = iter::once(123);The type of the resulting iterator might become incompatible with its usage
Looks for iterator combinator calls such as.take(x) or.skip(x)wherex is greater than the amount of items that an iterator will produce.
Taking or skipping more items than there are in an iterator either creates an iteratorwith all items from the original iterator or an iterator with no items at all.This is most likely not what the user intended to do.
for _ in [1, 2, 3].iter().take(4) {}Use instead:
for _ in [1, 2, 3].iter() {}This is a restriction lint which prevents the use of hash types (i.e.,HashSet andHashMap) in for loops.
Because hash types are unordered, when iterated through such as in afor loop, the values are returned inan undefined order. As a result, on redundant systems this may cause inconsistencies and anomalies.In addition, the unknown order of the elements may reduce readability or introduce other undesiredside effects.
let my_map = std::collections::HashMap::<i32, String>::new(); for (key, value) in my_map { /* ... */ }Use instead:
let my_map = std::collections::HashMap::<i32, String>::new(); let mut keys = my_map.keys().clone().collect::<Vec<_>>(); keys.sort(); for key in keys { let value = &my_map[key]; }Checks for usage of_.cloned().<func>() where call to.cloned() can be postponed.
It’s often inefficient to clone all elements of an iterator, when eventually, only someof them will be consumed.
Thislint removes the side of effect of cloning items in the iterator.A code that relies on that side-effect could fail.
vec.iter().cloned().take(10);vec.iter().cloned().last();Use instead:
vec.iter().take(10).cloned();vec.iter().last().cloned();Checks for usage of.skip(x).next() on iterators.
.nth(x) is cleaner
let some_vec = vec![0, 1, 2, 3];let bad_vec = some_vec.iter().skip(3).next();let bad_slice = &some_vec[..].iter().skip(3).next();The correct use would be:
let some_vec = vec![0, 1, 2, 3];let bad_vec = some_vec.iter().nth(3);let bad_slice = &some_vec[..].iter().nth(3);Checks for usage of.skip(0) on iterators.
This was likely intended to be.skip(1) to skip the first element, as.skip(0) doesnothing. If not, the call should be removed.
let v = vec![1, 2, 3];let x = v.iter().skip(0).collect::<Vec<_>>();let y = v.iter().collect::<Vec<_>>();assert_eq!(x, y);Checks for usage of.drain(..) onVec andVecDeque for iteration.
.into_iter() is simpler with better performance.
let mut foo = vec![0, 1, 2, 3];let bar: HashSet<usize> = foo.drain(..).collect();Use instead:
let foo = vec![0, 1, 2, 3];let bar: HashSet<usize> = foo.into_iter().collect();Looks foriter anditer_mut methods without an associatedIntoIterator for (&|&mut) Type implementation.
It’s not bad, but having them is idiomatic and allows the type to be used in for loops directly(for val in &iter {}), without having to first calliter() oriter_mut().
This lint focuses on providing an idiomatic API. Therefore, it will onlylint on types which are accessible outside of the crate. For internal types,theIntoIterator trait can be implemented on demand if it is actually needed.
struct MySlice<'a>(&'a [u8]);impl<'a> MySlice<'a> { pub fn iter(&self) -> std::slice::Iter<'a, u8> { self.0.iter() }}Use instead:
struct MySlice<'a>(&'a [u8]);impl<'a> MySlice<'a> { pub fn iter(&self) -> std::slice::Iter<'a, u8> { self.0.iter() }}impl<'a> IntoIterator for &MySlice<'a> { type Item = &'a u8; type IntoIter = std::slice::Iter<'a, u8>; fn into_iter(self) -> Self::IntoIter { self.iter() }}Checks for calling.step_by(0) on iterators which panics.
This very much looks like an oversight. Usepanic!() instead if youactually intend to panic.
for x in (0..100).step_by(0) { //..}Checks for calls toPath::join that start with a path separator (\\ or/).
If the argument toPath::join starts with a separator, it will overwritethe original path. If this is intentional, prefer usingPath::new instead.
Note the behavior is platform dependent. A leading\\ will be acceptedon unix systems as part of the file name
SeePath::join
let path = Path::new("/bin");let joined_path = path.join("/sh");assert_eq!(joined_path, PathBuf::from("/sh"));Use instead;
let path = Path::new("/bin");// If this was unintentional, remove the leading separatorlet joined_path = path.join("sh");assert_eq!(joined_path, PathBuf::from("/bin/sh"));// If this was intentional, create a new path insteadlet new = Path::new("/sh");assert_eq!(new, PathBuf::from("/sh"));Checks if you have variables whose name consists of justunderscores and digits.
It’s hard to memorize what a variable means without adescriptive name.
let _1 = 1;let ___1 = 1;let __1___2 = 11;Checks for largeconst arrays that shouldbe defined asstatic instead.
Performance: const variables are inlined upon use.Static items result in only one instance and has a fixed location in memory.
pub const a = [0u32; 1_000_000];Use instead:
pub static a = [0u32; 1_000_000];array-size-threshold: The maximum allowed size for arrays on the stack
(default:16384)
Warns if the digits of an integral or floating-pointconstant are grouped into groups thatare too large.
Negatively impacts readability.
let x: u64 = 6186491_8973511;Checks for large size differences between variants onenums.
Enum size is bounded by the largest variant. Having onelarge variant can penalize the memory layout of that enum.
This lint obviously cannot take the distribution ofvariants in your running program into account. It is possible that thesmaller variants make up less than 1% of all instances, in which casethe overhead is negligible and the boxing is counter-productive. Alwaysmeasure the change this lint suggests.
For types that implementCopy, the suggestion toBox a variant’sdata would require removing the trait impl. The types can of coursestill beClone, but that is worse ergonomically. Depending on theuse case it may be possible to store the large data in an auxiliarystructure (e.g. Arena or ECS).
The lint will ignore the impact of generic types to the type layout byassuming every type parameter is zero-sized. Depending on your use case,this may lead to a false positive.
enum Test { A(i32), B([i32; 8000]),}Use instead:
// Possibly betterenum Test2 { A(i32), B(Box<[i32; 8000]>),}enum-variant-size-threshold: The maximum size of an enum’s variant to avoid box suggestion
(default:200)
It checks for the size of aFuture created byasync fn orasync {}.
Due to the currentunideal implementation ofCoroutine,large size of aFuture may cause stack overflows.
async fn large_future(_x: [u8; 16 * 1024]) {}pub async fn trigger() { large_future([0u8; 16 * 1024]).await;}Box::pin the big future instead.
async fn large_future(_x: [u8; 16 * 1024]) {}pub async fn trigger() { Box::pin(large_future([0u8; 16 * 1024])).await;}future-size-threshold: The maximum byte size aFuture can have, before it triggers theclippy::large_futures lint
(default:16384)
Checks for the inclusion of large files viainclude_bytes!()orinclude_str!().
Including large files can undesirably increase the size of the binary produced by the compiler.This lint may be used to catch mistakes where an unexpectedly large file is included, ortemporarily to obtain a list of all large files.
let included_str = include_str!("very_large_file.txt");let included_bytes = include_bytes!("very_large_file.txt");Use instead:
use std::fs;// You can load the file at runtimelet string = fs::read_to_string("very_large_file.txt")?;let bytes = fs::read("very_large_file.txt")?;max-include-file-size: The maximum size of a file included viainclude_bytes!() orinclude_str!(), in bytes
(default:1000000)
Checks for local arrays that may be too large.
Large local arrays may cause stack overflow.
let a = [0u32; 1_000_000];array-size-threshold: The maximum allowed size for arrays on the stack
(default:16384)
Checks for functions that use a lot of stack space.
This often happens when constructing a large type, such as an array with a lot of elements,or constructingmany smaller-but-still-large structs, or copying around a lot of large types.
This lint is a more general version oflarge_stack_arraysthat is intended to look at functions as a whole instead of only individual array expressions inside of a function.
The stack region of memory is very limited in size (usuallymuch smaller than the heap) and attempting touse too much will result in a stack overflow and crash the program.To avoid this, you should consider allocating large types on the heap instead (e.g. by boxing them).
Keep in mind that the code path to construction of large types does not even need to be reachable;it purely needs toexist inside of the function to contribute to the stack size.For example, this causes a stack overflow even though the branch is unreachable:
fn main() { if false { let x = [0u8; 10000000]; // 10 MB stack array black_box(&x); }}False positives. The stack size that clippy sees is an estimated value and can be vastly differentfrom the actual stack usage after optimizations passes have run (especially true in release mode).Modern compilers are very smart and are able to optimize away a lot of unnecessary stack allocations.In debug mode however, it is usually more accurate.
This lint works by summing up the size of all variables that the user typed, variables that wereimplicitly introduced by the compiler for temporaries, function arguments and the return value,and comparing them against a (configurable, but high-by-default).
This function creates four 500 KB arrays on the stack. Quite big but just small enough to not triggerlarge_stack_arrays.However, looking at the function as a whole, it’s clear that this uses a lot of stack space.
struct QuiteLargeType([u8; 500_000]);fn foo() { // ... some function that uses a lot of stack space ... let _x1 = QuiteLargeType([0; 500_000]); let _x2 = QuiteLargeType([0; 500_000]); let _x3 = QuiteLargeType([0; 500_000]); let _x4 = QuiteLargeType([0; 500_000]);}Instead of doing this, allocate the arrays on the heap.This currently requires going through aVec first and then converting it to aBox:
struct NotSoLargeType(Box<[u8]>);fn foo() { let _x1 = NotSoLargeType(vec![0; 500_000].into_boxed_slice());// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Now heap allocated.// The size of `NotSoLargeType` is 16 bytes.// ...}stack-size-threshold: The maximum allowed stack size for functions in bytes
(default:512000)
Checks for functions taking arguments by value, wherethe argument type isCopy and large enough to be worth consideringpassing by reference. Does not trigger if the function is being exported,because that might induce API breakage, if the parameter is declared as mutable,or if the argument is aself.
Arguments passed by value might result in an unnecessaryshallow copy, taking up more space in the stack and requiring a call tomemcpy, which can be expensive.
#[derive(Clone, Copy)]struct TooLarge([u8; 2048]);fn foo(v: TooLarge) {}Use instead:
fn foo(v: &TooLarge) {}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
pass-by-value-size-limit: The minimum size (in bytes) to consider a type for passing by reference instead of by value.
(default:256)
Checks for usage of<integer>::max_value(),std::<integer>::MAX,std::<float>::EPSILON, etc.
All of these have been superseded by the associated constants on their respective types,such asi128::MAX. These legacy items may be deprecated in a future version of rust.
let eps = std::f32::EPSILON;Use instead:
let eps = f32::EPSILON;msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for items that implement.len() but not.is_empty().
It is good custom to have both methods, because forsome data structures, asking about the length will be a costly operation,whereas.is_empty() can usually answer in constant time. Also it used tolead to false positives on thelen_zero lint – currently thatlint will ignore such entities.
impl X { pub fn len(&self) -> usize { .. }}Checks for getting the length of something via.len()just to compare to zero, and suggests using.is_empty() where applicable.
Some structures can answer.is_empty() much fasterthan calculating their length. So it is good to get into the habit of using.is_empty(), and having it is cheap.Besides, it makes the intent clearer than a manual comparison in some contexts.
if x.len() == 0 { ..}if y.len() != 0 { ..}instead use
if x.is_empty() { ..}if !y.is_empty() { ..}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks forlet-bindings, which are subsequentlyreturned.
It is just extraneous code. Remove it to make your codemore rusty.
In the case of some temporaries, e.g. locks, eliding the variable binding could leadto deadlocks. Seethis issue.This could become relevant if the code is later changed to use the code that would have beenbound without first assigning it to a let-binding.
fn foo() -> String { let x = String::new(); x}instead, use
fn foo() -> String { String::new()}Checks forlet _ = <expr> where the resulting type of expr implementsFuture
Futures must be polled for work to be done. The original intention was most likely to await the futureand ignore the resulting value.
async fn foo() -> Result<(), ()> { Ok(())}let _ = foo();Use instead:
async fn foo() -> Result<(), ()> { Ok(())}let _ = foo().await;Checks forlet _ = sync_lock. This supportsmutex andrwlock inparking_lot. Forstd locks see therustc lintlet_underscore_lock
This statement immediately drops the lock instead ofextending its lifetime to the end of the scope, which is often not intended.To extend lock lifetime to the end of the scope, use an underscore-prefixedname instead (i.e. _lock). If you want to explicitly drop the lock,std::mem::drop conveys your intention better and is less error-prone.
let _ = mutex.lock();Use instead:
let _lock = mutex.lock();Checks forlet _ = <expr> where expr is#[must_use]
To ensure that all#[must_use] types are used rather than ignored.
fn f() -> Result<u32, u32> { Ok(0)}let _ = f();// is_ok() is marked #[must_use]let _ = f().is_ok();Checks forlet _ = <expr> without a type annotation, and suggests to either provide one,or remove thelet keyword altogether.
Thelet _ = <expr> expression ignores the value of<expr>, but will continue to do so evenif the type were to change, thus potentially introducing subtle bugs. By supplying a typeannotation, one will be forced to re-visit the decision to ignore the value in such cases.
The_ = <expr> is not properly supported by some tools (e.g. IntelliJ) and may seem oddto many developers. This lint also partially overlaps with the otherlet_underscore_*lints.
fn foo() -> Result<u32, ()> { Ok(123)}let _ = foo();Use instead:
fn foo() -> Result<u32, ()> { Ok(123)}// Either provide a type annotation:let _: Result<u32, ()> = foo();// …or drop the let keyword:_ = foo();Checks for binding a unit value.
A unit value cannot usefully be used anywhere. Sobinding one is kind of pointless.
let x = { 1;};Detects when a variable is declared with an explicit type of_.
It adds noise,: _ provides zero clarity or utility.
let my_number: _ = 1;Use instead:
let my_number = 1;Checks for usage oflines.filter_map(Result::ok) orlines.flat_map(Result::ok)whenlines has typestd::io::Lines.
Lines instances might produce a never-ending stream ofErr, in which casefilter_map(Result::ok) will enter an infinite loop while waiting for anOk variant. Callingnext() once is sufficient to enter the infinite loop,even in the absence of explicit loops in the user code.
This situation can arise when working with user-provided paths. On some platforms,std::fs::File::open(path) might returnOk(fs) even whenpath is a directory,but any later attempt to read fromfs will return an error.
This lint suggests replacingfilter_map() orflat_map() applied to aLinesinstance in all cases. There are two cases where the suggestion might not beappropriate or necessary:
Lines instance can never produce any error, or if an error is producedonly once just before terminating the iterator, usingmap_while() is notnecessary but will not do any harm.Lines instance can produce intermittent errors then recover and producesuccessful results, usingmap_while() would stop at the first error.let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok);// If "some-path" points to a directory, the next statement never terminates:let first_line: Option<String> = lines.next();Use instead:
let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok);let first_line: Option<String> = lines.next();msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage of anyLinkedList, suggesting to use aVec or aVecDeque (formerly calledRingBuf).
Gankra says:
The TL;DR of
LinkedListis that it’s built on a massive amount ofpointers and indirection.It wastes memory, it has terrible cache locality, and is all-around slow.RingBuf, while“only” amortized for push/pop, should be faster in the general case foralmost every possibleworkload, and isn’t even amortized at all if you can predict the capacityyou need.
LinkedLists are only really good if you’re doing a lot of merging orsplitting of lists.This is because they can just mangle some pointers instead of actuallycopying the data. Evenif you’re doing a lot of insertion in the middle of the list,RingBufcan still be betterbecause of how expensive it is to seek to the middle of aLinkedList.
False positives – the instances where using aLinkedList makes sense are few and far between, but they can still happen.
let x: LinkedList<usize> = LinkedList::new();avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Checks for lint groups with the same priority as lints in theCargo.toml[lints] table.
This lint will be removed oncecargo#12918is resolved.
The order of lints in the[lints] is ignored, to have a lint override a group thepriority field needs to be used, otherwise the sort order is undefined.
Does not check lints inherited usinglints.workspace = true
[lints.clippy]pedantic = "warn"similar_names = "allow"Use instead:
[lints.clippy]pedantic = { level = "warn", priority = -1 }similar_names = "allow"Checks if string literals have formatting arguments outside of macrosusing them (likeformat!).
It will likely not generate the expected content.
let x: Option<usize> = None;let y = "hello";x.expect("{y:?}");Use instead:
let x: Option<usize> = None;let y = "hello";x.expect(&format!("{y:?}"));Checks for the usage of theto_le_bytes method and/or the functionfrom_le_bytes.
To ensure use of big-endian or the target’s endianness rather than little-endian.
let _x = 2i32.to_le_bytes();let _y = 2i64.to_le_bytes();Checks for whole number float literals thatcannot be represented as the underlying type without loss.
If the value was intended to be exact, it will not be.This may be especially surprising when the lost precision is to the left of the decimal point.
let _: f32 = 16_777_217.0; // 16_777_216.0Use instead:
let _: f32 = 16_777_216.0;let _: f64 = 16_777_217.0;Looks for macros that expand metavariables in an unsafe block.
This hides an unsafe block and allows the user of the macro to write unsafe code without an explicitunsafe block at callsite, making it possible to perform unsafe operations in seemingly safe code.
The macro should be restructured so that these metavariables are referenced outside of unsafe blocksand that the usual unsafety checks apply to the macro argument.
This is usually done by binding it to a variable outside of the unsafe blockand then using that variable inside of the block as shown in the example, or by referencing it a second timein a safe context, e.g.if false { $expr }.
Due to how macros are represented in the compiler at the time Clippy runs its lints,it’s not possible to look for metavariables in macro definitions directly.
Instead, this lint looks at expansions of macros.This leads to false negatives for macros that are never actually invoked.
By default, this lint is rather conservative and will only emit warnings on publicly-exportedmacros from the same crate, because oftentimes private internal macros are one-off macros wherethis lint would just be noise (e.g. macros that generateimpl blocks).The default behavior should help with preventing a high number of such false positives,however it can be configured to also emit warnings in private macros if desired.
/// Gets the first element of a slicemacro_rules! first { ($slice:expr) => { unsafe { let slice = $slice; // ⚠️ expansion inside of `unsafe {}` assert!(!slice.is_empty()); // SAFETY: slice is checked to have at least one element slice.first().unwrap_unchecked() } }}assert_eq!(*first!(&[1i32]), 1);// This will compile as a consequence (note the lack of `unsafe {}`)assert_eq!(*first!(std::hint::unreachable_unchecked() as &[i32]), 1);Use instead:
macro_rules! first { ($slice:expr) => {{ let slice = $slice; // ✅ outside of `unsafe {}` unsafe { assert!(!slice.is_empty()); // SAFETY: slice is checked to have at least one element slice.first().unwrap_unchecked() } }}}assert_eq!(*first!(&[1]), 1);// This won't compile:assert_eq!(*first!(std::hint::unreachable_unchecked() as &[i32]), 1);warn-unsafe-macro-metavars-in-private-macros: Whether to also emit warnings for unsafe blocks with metavariable expansions inprivate macros.
(default:false)
Checks for#[macro_use] use....
Since the Rust 2018 edition you can importmacro’s directly, this is considered idiomatic.
#[macro_use]extern crate some_crate;fn main() { some_macro!();}Use instead:
use some_crate::some_macro;fn main() { some_macro!();}Checks for recursion using the entrypoint.
Apart from special setups (which we could detect following attributes like #![no_std]),recursing into main() seems like an unintuitive anti-pattern we should be able to detect.
fn main() { main();}Detects patterns likeif a > b { a - b } else { b - a } and suggests usinga.abs_diff(b).
Usingabs_diff is shorter, more readable, and avoids control flow.
if a > b { a - b} else { b - a}Use instead:
a.abs_diff(b)msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Detectsif-then-panic! that can be replaced withassert!.
assert! is simpler thanif-then-panic!.
let sad_people: Vec<&str> = vec![];if !sad_people.is_empty() { panic!("there are sad people: {:?}", sad_people);}Use instead:
let sad_people: Vec<&str> = vec![];assert!(sad_people.is_empty(), "there are sad people: {:?}", sad_people);It checks for manual implementations ofasync functions.
It’s more idiomatic to use the dedicated syntax.
use std::future::Future;fn foo() -> impl Future<Output = i32> { async { 42 } }Use instead:
async fn foo() -> i32 { 42 }Checks for usage ofsize_of::<T>() * 8 whenT::BITS is available.
Can be written as the shorterT::BITS.
size_of::<usize>() * 8;Use instead:
usize::BITS as usize;msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for the manual creation of C strings (a string with aNUL byte at the end), eitherthrough one of theCStr constructor functions, or more plainly by calling.as_ptr()on a (byte) string literal with a hardcoded\0 byte at the end.
This can be written more concisely usingc"str" literals and is also less error-prone,because the compiler checks for interiorNUL bytes and the terminatingNUL byte is inserted automatically.
fn needs_cstr(_: &CStr) {}needs_cstr(CStr::from_bytes_with_nul(b"Hello\0").unwrap());unsafe { libc::puts("World\0".as_ptr().cast()) }Use instead:
fn needs_cstr(_: &CStr) {}needs_cstr(c"Hello");unsafe { libc::puts(c"World".as_ptr()) }msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Identifies good opportunities for a clamp function from std or core, and suggests using it.
clamp is much shorter, easier to read, and doesn’t use any control flow.
This lint will only trigger if max and min are known at compile time, and max isgreater than min.
If the clamped variable is NaN this suggestion will cause the code to propagate NaNrather than returning eithermax ormin.
clamp functions will panic ifmax < min,max.is_nan(), ormin.is_nan().Some may consider panicking in these situations to be desirable, but it also mayintroduce panicking where there wasn’t any before.
See alsothe discussion in thePR.
if input > max { max} else if input < min { min} else { input}input.max(min).min(max)match input { x if x > max => max, x if x < min => min, x => x,}let mut x = input;if x < min { x = min; }if x > max { x = max; }Use instead:
input.clamp(min, max)msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage ofiter().any() on slices when it can be replaced withcontains() and suggests doing so.
contains() is more concise and idiomatic, while also being faster in some cases.
fn foo(values: &[u8]) -> bool { values.iter().any(|&v| v == 10)}Use instead:
fn foo(values: &[u8]) -> bool { values.contains(&10)}Checks for casts of small constant literals ormem::align_of results to raw pointers.
This creates a dangling pointer and is better expressed as{std,core}::ptr::{dangling,dangling_mut}.
let ptr = 4 as *const u32;let aligned = std::mem::align_of::<u32>() as *const u32;let mut_ptr: *mut i64 = 8 as *mut _;Use instead:
let ptr = std::ptr::dangling::<u32>();let aligned = std::ptr::dangling::<u32>();let mut_ptr: *mut i64 = std::ptr::dangling_mut();Checks for an expression like(x + (y - 1)) / y which is a common manual reimplementationofx.div_ceil(y).
It’s simpler, clearer and more readable.
let x: i32 = 7;let y: i32 = 4;let div = (x + (y - 1)) / y;Use instead:
#![feature(int_roundings)]let x: i32 = 7;let y: i32 = 4;let div = x.div_ceil(y);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage ofmatch which could be implemented usingfilter
Using thefilter method is clearer and more concise.
match Some(0) { Some(x) => if x % 2 == 0 { Some(x) } else { None }, None => None,};Use instead:
Some(0).filter(|&x| x % 2 == 0);Checks for usage of_.filter(_).map(_) that can be written more simplyasfilter_map(_).
Redundant code in thefilter andmap operations is poor style andless performant.
(0_i32..10) .filter(|n| n.checked_add(1).is_some()) .map(|n| n.checked_add(1).unwrap());Use instead:
(0_i32..10).filter_map(|n| n.checked_add(1));Checks for manual implementations of Iterator::find
It doesn’t affect performance, but usingfind is shorter and easier to read.
fn example(arr: Vec<i32>) -> Option<i32> { for el in arr { if el == 1 { return Some(el); } } None}Use instead:
fn example(arr: Vec<i32>) -> Option<i32> { arr.into_iter().find(|&el| el == 1)}Checks for usage of_.find(_).map(_) that can be written more simplyasfind_map(_).
Redundant code in thefind andmap operations is poor style andless performant.
(0_i32..10) .find(|n| n.checked_add(1).is_some()) .map(|n| n.checked_add(1).unwrap());Use instead:
(0_i32..10).find_map(|n| n.checked_add(1));Checks for unnecessaryif let usage in a for loopwhere only theSome orOk variant of the iterator element is used.
It is verbose and can be simplifiedby first calling theflatten method on theIterator.
let x = vec![Some(1), Some(2), Some(3)];for n in x { if let Some(n) = n { println!("{}", n); }}Use instead:
let x = vec![Some(1), Some(2), Some(3)];for n in x.into_iter().flatten() { println!("{}", n);}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for cases whereBuildHasher::hash_one can be used.
It is more concise to use thehash_one method.
use std::hash::{BuildHasher, Hash, Hasher};use std::collections::hash_map::RandomState;let s = RandomState::new();let value = vec![1, 2, 3];let mut hasher = s.build_hasher();value.hash(&mut hasher);let hash = hasher.finish();Use instead:
use std::hash::BuildHasher;use std::collections::hash_map::RandomState;let s = RandomState::new();let value = vec![1, 2, 3];let hash = s.hash_one(&value);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for manual case-insensitive ASCII comparison.
Theeq_ignore_ascii_case method is faster because it does not allocatememory for the new strings, and it is more readable.
fn compare(a: &str, b: &str) -> bool { a.to_ascii_lowercase() == b.to_ascii_lowercase() || a.to_ascii_lowercase() == "abc"}Use instead:
fn compare(a: &str, b: &str) -> bool { a.eq_ignore_ascii_case(b) || a.eq_ignore_ascii_case("abc")}Checks for uses ofmap which return the original item.
inspect is both clearer in intent and shorter.
let x = Some(0).map(|x| { println!("{x}"); x });Use instead:
let x = Some(0).inspect(|x| println!("{x}"));Lints subtraction betweenInstant::now() and anotherInstant.
It is easy to accidentally writeprev_instant - Instant::now(), which will always be 0nsasInstant subtraction saturates.
prev_instant.elapsed() also more clearly signals intention.
use std::time::Instant;let prev_instant = Instant::now();let duration = Instant::now() - prev_instant;Use instead:
use std::time::Instant;let prev_instant = Instant::now();let duration = prev_instant.elapsed();Suggests to use dedicated built-in methods,is_ascii_(lowercase|uppercase|digit|hexdigit) for checking on correspondingascii range
Using the built-in functions is more readable and makes itclear that it’s not a specific subset of characters, but allASCII (lowercase|uppercase|digit|hexdigit) characters.
fn main() { assert!(matches!('x', 'a'..='z')); assert!(matches!(b'X', b'A'..=b'Z')); assert!(matches!('2', '0'..='9')); assert!(matches!('x', 'A'..='Z' | 'a'..='z')); assert!(matches!('C', '0'..='9' | 'a'..='f' | 'A'..='F')); ('0'..='9').contains(&'0'); ('a'..='z').contains(&'a'); ('A'..='Z').contains(&'A');}Use instead:
fn main() { assert!('x'.is_ascii_lowercase()); assert!(b'X'.is_ascii_uppercase()); assert!('2'.is_ascii_digit()); assert!('x'.is_ascii_alphabetic()); assert!('C'.is_ascii_hexdigit()); '0'.is_ascii_digit(); 'a'.is_ascii_lowercase(); 'A'.is_ascii_uppercase();}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for manualis_finite reimplementations(i.e.,x != <float>::INFINITY && x != <float>::NEG_INFINITY).
The methodis_finite is shorter and more readable.
if x != f32::INFINITY && x != f32::NEG_INFINITY {}if x.abs() < f32::INFINITY {}Use instead:
if x.is_finite() {}if x.is_finite() {}Checks for manualis_infinite reimplementations(i.e.,x == <float>::INFINITY || x == <float>::NEG_INFINITY).
The methodis_infinite is shorter and more readable.
if x == f32::INFINITY || x == f32::NEG_INFINITY {}Use instead:
if x.is_infinite() {}Checks for manual implementation of.is_multiple_of() onunsigned integer types.
a.is_multiple_of(b) is a clearer way to check for divisibilityofa byb. This expression can never panic.
if a % b == 0 { println!("{a} is divisible by {b}");}Use instead:
if a.is_multiple_of(b) { println!("{a} is divisible by {b}");}Checks for expressions likex.count_ones() == 1 orx & (x - 1) == 0, with x and unsigned integer, which may be manualreimplementations ofx.is_power_of_two().
Manual reimplementations ofis_power_of_two increase code complexity for little benefit.
let a: u32 = 4;let result = a.count_ones() == 1;Use instead:
let a: u32 = 4;let result = a.is_power_of_two();msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage ofoption.map(f).unwrap_or_default() andresult.map(f).unwrap_or_default() where f is a function or closure that returns thebool type.Also checks for equality comparisons likeoption.map(f) == Some(true) andresult.map(f) == Ok(true).
Readability. These can be written more concisely asoption.is_some_and(f) andresult.is_ok_and(f).
option.map(|a| a > 10).unwrap_or_default();result.map(|a| a > 10).unwrap_or_default();option.map(|a| a > 10) == Some(true);result.map(|a| a > 10) == Ok(true);option.map(|a| a > 10) != Some(true);result.map(|a| a > 10) != Ok(true);Use instead:
option.is_some_and(|a| a > 10);result.is_ok_and(|a| a > 10);option.is_some_and(|a| a > 10);result.is_ok_and(|a| a > 10);option.is_none_or(|a| a > 10);!result.is_ok_and(|a| a > 10);Warn of cases wherelet...else could be used
let...else provides a standard construct for this patternthat people can easily recognize. It’s also more compact.
let v = if let Some(v) = w { v } else { return };Could be written:
let Some(v) = w else { return };matches-for-let-else: Whether the matches should be considered by the lint, and whether there shouldbe filtering for common types.
(default:"WellKnownTypes")
msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for references onstd::path::MAIN_SEPARATOR.to_string() usedto build a&str.
There exists astd::path::MAIN_SEPARATOR_STR which does not requirean extra memory allocation.
let s: &str = &std::path::MAIN_SEPARATOR.to_string();Use instead:
let s: &str = std::path::MAIN_SEPARATOR_STR;Checks for usage ofmatch which could be implemented usingmap
Using themap method is clearer and more concise.
match Some(0) { Some(x) => Some(x + 1), None => None,};Use instead:
Some(0).map(|x| x + 1);Checks for for-loops that manually copy items betweenslices that could be optimized by having a memcpy.
It is not as fast as a memcpy.
for i in 0..src.len() { dst[i + 64] = src[i];}Use instead:
dst[64..(src.len() + 64)].clone_from_slice(&src[..]);Checks for manual implementation ofmidpoint.
Using(x + y) / 2 might cause an overflow on the intermediateaddition result.
let c = (a + 10) / 2;Use instead:
let c = u32::midpoint(a, 10);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for.rev().next() on aDoubleEndedIterator
.next_back() is cleaner.
foo.iter().rev().next();Use instead:
foo.iter().next_back();Checks for manual implementations of the non-exhaustive pattern.
Using the #[non_exhaustive] attribute expresses better the intentand allows possible optimizations when applied to enums.
struct S { pub a: i32, pub b: i32, _c: (),}enum E { A, B, #[doc(hidden)] _C,}struct T(pub i32, pub i32, ());Use instead:
#[non_exhaustive]struct S { pub a: i32, pub b: i32,}#[non_exhaustive]enum E { A, B,}#[non_exhaustive]struct T(pub i32, pub i32);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for manual implementation of.ok() or.err()onResult values.
Using.ok() or.err() rather than amatch orif let is less complex and more readable.
let a = match func() { Ok(v) => Some(v), Err(_) => None,};let b = if let Err(v) = func() { Some(v)} else { None};Use instead:
let a = func().ok();let b = func().err();Finds patterns that reimplementOption::ok_or.
Concise code helps focusing on behavior instead of boilerplate.
let foo: Option<i32> = None;foo.map_or(Err("error"), |v| Ok(v));Use instead:
let foo: Option<i32> = None;foo.ok_or("error");This detects various manual reimplementations ofOption::as_slice.
Those implementations are both more complex than callingas_sliceand unlike that incur a branch, pessimizing performance and leadingto more generated code.
_ = opt.as_ref().map_or(&[][..], std::slice::from_ref);_ = match opt.as_ref() { Some(f) => std::slice::from_ref(f), None => &[],};Use instead:
_ = opt.as_slice();_ = opt.as_slice();msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for manualchar comparison in string patterns
This can be written more concisely using achar or an array ofchar.This is more readable and more optimized when comparing to only onechar.
"Hello World!".trim_end_matches(|c| c == '.' || c == ',' || c == '!' || c == '?');Use instead:
"Hello World!".trim_end_matches(['.', ',', '!', '?']);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for expressions likex >= 3 && x < 8 that couldbe more readably expressed as(3..8).contains(x).
contains expresses the intent better and has lessfailure modes (such as fencepost errors or using|| instead of&&).
// givenlet x = 6;assert!(x >= 3 && x < 8);Use instead:
assert!((3..8).contains(&x));msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Looks for combined OR patterns that are all contained in a specific range,e.g.6 | 4 | 5 | 9 | 7 | 8 can be rewritten as4..=9.
Using an explicit range is more concise and easier to read.
This lint intentionally does not handle numbers greater thani128::MAX foru128 literalsin order to support negative numbers.
let x = 6;let foo = matches!(x, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10);Use instead:
let x = 6;let foo = matches!(x, 1..=10);Checks for an expression like((x % 4) + 4) % 4 which is a common manual reimplementationofx.rem_euclid(4).
It’s simpler and more readable.
let x: i32 = 24;let rem = ((x % 4) + 4) % 4;Use instead:
let x: i32 = 24;let rem = x.rem_euclid(4);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks forrepeat().take() that can be replaced withrepeat_n().
Usingrepeat_n() is more concise and clearer. Also,repeat_n() is sometimes faster thanrepeat().take() when the type of the element is non-trivial to clone because the original value can be reused for the last.next() call rather than always cloning.
let _ = std::iter::repeat(10).take(3);Use instead:
let _ = std::iter::repeat_n(10, 3);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for code to be replaced by.retain().
.retain() is simpler and avoids needless allocation.
let mut vec = vec![0, 1, 2];vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect();vec = vec.into_iter().filter(|x| x % 2 == 0).collect();Use instead:
let mut vec = vec![0, 1, 2];vec.retain(|x| x % 2 == 0);vec.retain(|x| x % 2 == 0);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
It detects manual bit rotations that could be rewritten using standardfunctionsrotate_left orrotate_right.
Calling the function better conveys the intent.
Currently, the lint only catches shifts by constant amount.
let x = 12345678_u32;let _ = (x >> 8) | (x << 24);Use instead:
let x = 12345678_u32;let _ = x.rotate_right(8);Checks for.checked_add/sub(x).unwrap_or(MAX/MIN).
These can be written simply withsaturating_add/sub methods.
let add = x.checked_add(y).unwrap_or(u32::MAX);let sub = x.checked_sub(y).unwrap_or(u32::MIN);can be written using dedicated methods for saturating addition/subtraction as:
let add = x.saturating_add(y);let sub = x.saturating_sub(y);Checks for manually filling a slice with a value.
Using thefill method is more idiomatic and concise.
let mut some_slice = [1, 2, 3, 4, 5];for i in 0..some_slice.len() { some_slice[i] = 0;}Use instead:
let mut some_slice = [1, 2, 3, 4, 5];some_slice.fill(0);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Whena is&[T], detecta.len() * size_of::<T>() and suggestsize_of_val(a)instead.
let newlen = data.len() * size_of::<i32>();Use instead:
let newlen = size_of_val(data);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage ofstr::splitn(2, _)
split_once is both clearer in intent and slightly more efficient.
let s = "key=value=add";let (key, value) = s.splitn(2, '=').next_tuple()?;let value = s.splitn(2, '=').nth(1)?;let mut parts = s.splitn(2, '=');let key = parts.next()?;let value = parts.next()?;Use instead:
let s = "key=value=add";let (key, value) = s.split_once('=')?;let value = s.split_once('=')?.1;let (key, value) = s.split_once('=')?;The multiple statement variant currently only detectsiter.next()?/iter.next().unwrap()in two separatelet statements that immediately follow thesplitn()
msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for manual implementations ofstr::repeat
These are both harder to read, as well as less performant.
let x: String = std::iter::repeat('x').take(10).collect();Use instead:
let x: String = "x".repeat(10);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage of"" to create aString, such as"".to_string(),"".to_owned(),String::from("") and others.
Different ways of creating an empty string makes your code less standardized, which canbe confusing.
let a = "".to_string();let b: String = "".into();Use instead:
let a = String::new();let b = String::new();Suggests usingstrip_{prefix,suffix} overstr::{starts,ends}_with and slicing usingthe pattern’s length.
Usingstr:strip_{prefix,suffix} is safer and may have better performance as there is noslicing which may panic and the compiler does not need to insert this panic code. It isalso sometimes more readable as it removes the need for duplicating or storing the patternused bystr::{starts,ends}_with and in the slicing.
let s = "hello, world!";if s.starts_with("hello, ") { assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");}Use instead:
let s = "hello, world!";if let Some(end) = s.strip_prefix("hello, ") { assert_eq!(end.to_uppercase(), "WORLD!");}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for manual swapping.
Note that the lint will not be emitted in const blocks, as the suggestion would not be applicable.
Thestd::mem::swap function exposes the intent betterwithout deinitializing or copying either variable.
let mut a = 42;let mut b = 1337;let t = b;b = a;a = t;Use std::mem::swap():
let mut a = 1;let mut b = 2;std::mem::swap(&mut a, &mut b);Checks for usage ofIterator::fold with a type that implementsTry.
The code should usetry_fold instead, which short-circuits on failure, thus opening thedoor for additional optimizations not possible withfold as rustc can guarantee thefunction is never called onNone,Err, etc., alleviating otherwise necessary checks. It’salso slightly more idiomatic.
This lint doesn’t take into account whether a function does something on the failure case,i.e., whether short-circuiting will affect behavior. Refactoring totry_fold is notdesirable in those cases.
vec![1, 2, 3].iter().fold(Some(0i32), |sum, i| sum?.checked_add(*i));Use instead:
vec![1, 2, 3].iter().try_fold(0i32, |sum, i| sum.checked_add(*i));msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Finds patterns that reimplementOption::unwrap_or orResult::unwrap_or.
Concise code helps focusing on behavior instead of boilerplate.
let foo: Option<i32> = None;match foo { Some(v) => v, None => 1,};Use instead:
let foo: Option<i32> = None;foo.unwrap_or(1);Checks if amatch orif let expression can be simplified using.unwrap_or_default().
It can be done in one call with.unwrap_or_default().
let x: Option<String> = Some(String::new());let y: String = match x { Some(v) => v, None => String::new(),};let x: Option<Vec<String>> = Some(Vec::new());let y: Vec<String> = if let Some(v) = x { v} else { Vec::new()};Use instead:
let x: Option<String> = Some(String::new());let y: String = x.unwrap_or_default();let x: Option<Vec<String>> = Some(Vec::new());let y: Vec<String> = x.unwrap_or_default();Looks for loops that check for emptiness of aVec in the condition and pop an elementin the body as a separate operation.
Such loops can be written in a more idiomatic way by using a while-let loop and directlypattern matching on the return value ofVec::pop().
let mut numbers = vec![1, 2, 3, 4, 5];while !numbers.is_empty() { let number = numbers.pop().unwrap(); // use `number`}Use instead:
let mut numbers = vec![1, 2, 3, 4, 5];while let Some(number) = numbers.pop() { // use `number`}Checks for too many variables whose name consists of asingle character.
It’s hard to memorize what a variable means without adescriptive name.
let (a, b, c, d, e, f, g) = (...);single-char-binding-names-threshold: The maximum number of single char bindings a scope may have
(default:4)
Checks for usage of.map(…), followed by.all(identity) or.any(identity).
The.all(…) or.any(…) methods can be called directly in place of.map(…).
let e1 = v.iter().map(|s| s.is_empty()).all(|a| a);let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity);Use instead:
let e1 = v.iter().all(|s| s.is_empty());let e2 = v.iter().any(|s| s.is_empty());Checks for usage ofmap(|x| x.clone()) ordereferencing closures forCopy types, onIterator orOption,and suggestscloned() orcopied() instead
Readability, this can be written more concisely
let x = vec![42, 43];let y = x.iter();let z = y.map(|i| *i);The correct use would be:
let x = vec![42, 43];let y = x.iter();let z = y.cloned();msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage of_.map(_).collect::<Result<(), _>().
Usingtry_for_each instead is more readable and idiomatic.
(0..3).map(|t| Err(t)).collect::<Result<(), _>>();Use instead:
(0..3).try_for_each(|t| Err(t));Checks for usage ofcontains_key +insert onHashMaporBTreeMap.
Usingentry is more efficient.
The suggestion may have type inference errors in some cases. e.g.
let mut map = std::collections::HashMap::new();let _ = if !map.contains_key(&0) { map.insert(0, 0)} else { None};if !map.contains_key(&k) { map.insert(k, v);}Use instead:
map.entry(k).or_insert(v);Checks for instances ofmap_err(|_| Some::Enum)
Thismap_err throws away the original error rather than allowing the enum tocontain and report the cause of the error.
Before:
use std::fmt;#[derive(Debug)]enum Error { Indivisible, Remainder(u8),}impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Indivisible => write!(f, "could not divide input by three"), Error::Remainder(remainder) => write!( f, "input is not divisible by three, remainder = {}", remainder ), } }}impl std::error::Error for Error {}fn divisible_by_3(input: &str) -> Result<(), Error> { input .parse::<i32>() .map_err(|_| Error::Indivisible) .map(|v| v % 3) .and_then(|remainder| { if remainder == 0 { Ok(()) } else { Err(Error::Remainder(remainder as u8)) } })}After:
use std::{fmt, num::ParseIntError};#[derive(Debug)]enum Error { Indivisible(ParseIntError), Remainder(u8),}impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Indivisible(_) => write!(f, "could not divide input by three"), Error::Remainder(remainder) => write!( f, "input is not divisible by three, remainder = {}", remainder ), } }}impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::Indivisible(source) => Some(source), _ => None, } }}fn divisible_by_3(input: &str) -> Result<(), Error> { input .parse::<i32>() .map_err(Error::Indivisible) .map(|v| v % 3) .and_then(|remainder| { if remainder == 0 { Ok(()) } else { Err(Error::Remainder(remainder as u8)) } })}Checks for usage of_.map(_).flatten(_) onIterator andOption
Readability, this can be written more concisely as_.flat_map(_) forIterator or_.and_then(_) forOption
let vec = vec![vec![1]];let opt = Some(5);vec.iter().map(|x| x.iter()).flatten();opt.map(|x| Some(x * 2)).flatten();Use instead:
vec.iter().flat_map(|x| x.iter());opt.and_then(|x| Some(x * 2));Checks for instances ofmap(f) wheref is the identity function.
It can be written more concisely without the call tomap.
let x = [1, 2, 3];let y: Vec<_> = x.iter().map(|x| x).map(|x| 2*x).collect();Use instead:
let x = [1, 2, 3];let y: Vec<_> = x.iter().map(|x| 2*x).collect();Checks for usage ofoption.map(_).unwrap_or(_) oroption.map(_).unwrap_or_else(_) orresult.map(_).unwrap_or_else(_).
Readability, these can be written more concisely (resp.) asoption.map_or(_, _),option.map_or_else(_, _) andresult.map_or_else(_, _).
The order of the arguments is not in execution order
option.map(|a| a + 1).unwrap_or(0);option.map(|a| a > 10).unwrap_or(false);result.map(|a| a + 1).unwrap_or_else(some_function);Use instead:
option.map_or(0, |a| a + 1);option.is_some_and(|a| a > 10);result.map_or_else(some_function, |a| a + 1);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks forIterator::map over ranges without using the parameter whichcould be more clearly expressed usingstd::iter::repeat(...).take(...)orstd::iter::repeat_n.
It expresses the intent more clearly totake the correct number of timesfrom a generating function than to apply a closure to each number in arange only to discard them.
let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect();Use instead:
let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect();This lint may suggest replacing aMap<Range> with aTake<RepeatWith>.The former implements some traits that the latter does not, such asDoubleEndedIterator.
msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for match which is used to add a reference to anOption value.
Usingas_ref() oras_mut() instead is shorter.
let x: Option<()> = None;let r: Option<&()> = match x { None => None, Some(ref v) => Some(v),};Use instead:
let x: Option<()> = None;let r: Option<&()> = x.as_ref();Checks for matches where match expression is abool. Itsuggests to replace the expression with anif...else block.
It makes the code less readable.
let condition: bool = true;match condition { true => foo(), false => bar(),}Use if/else instead:
let condition: bool = true;if condition { foo();} else { bar();}Checks formatch orif let expressions producing abool that could be written usingmatches!
Readability and needless complexity.
This lint falsely triggers, if there are arms withcfg attributes that remove an arm evaluating tofalse.
let x = Some(5);let a = match x { Some(0) => true, _ => false,};let a = if let Some(0) = x { true} else { false};Use instead:
let x = Some(5);let a = matches!(x, Some(0));msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Nothing. This lint has been deprecated
clippy::indexing_slicing covers indexing and slicing onVec<_>.
Checks for overlapping match arms.
It is likely to be an error and if not, makes the codeless obvious.
let x = 5;match x { 1..=10 => println!("1 ... 10"), 5..=15 => println!("5 ... 15"), _ => (),}Checks for matches where all arms match a reference,suggesting to remove the reference and deref the matched expressioninstead. It also checks forif let &foo = bar blocks.
It just makes the code less readable. That referencedestructuring adds nothing to the code.
match x { &A(ref y) => foo(y), &B => bar(), _ => frob(&x),}Use instead:
match *x { A(ref y) => foo(y), B => bar(), _ => frob(x),}Checks for unnecessaryok() inwhile let.
Callingok() inwhile let is unnecessary, instead matchonOk(pat)
while let Some(value) = iter.next().ok() { vec.push(value)}if let Some(value) = iter.next().ok() { vec.push(value)}Use instead:
while let Ok(value) = iter.next() { vec.push(value)}if let Ok(value) = iter.next() { vec.push(value)}Checks formatch with identical arm bodies.
Note: Does not lint on wildcards if thenon_exhaustive_omitted_patterns_lint feature isenabled and disallowed.
This is probably a copy & paste error. If arm bodiesare the same on purpose, you can factor themusing|.
match foo { Bar => bar(), Quz => quz(), Baz => bar(), // <= oops}This should probably be
match foo { Bar => bar(), Quz => quz(), Baz => baz(), // <= fixed}or if the original code was not a typo:
match foo { Bar | Baz => bar(), // <= shows the intent better Quz => quz(),}Checks for useless match that binds to only one value.
Readability and needless complexity.
Suggested replacements may be incorrect whenmatchis actually binding temporary value, bringing a ‘dropped while borrowed’ error.
match (a, b) { (c, d) => { // useless match }}Use instead:
let (c, d) = (a, b);Checks formatch expressions modifying the case of a string with non-compliant arms
The arm is unreachable, which is likely a mistake
match &*text.to_ascii_lowercase() { "foo" => {}, "Bar" => {}, _ => {},}Use instead:
match &*text.to_ascii_lowercase() { "foo" => {}, "bar" => {}, _ => {},}Checks for arm which matches all errors withErr(_)and take drastic actions likepanic!.
It is generally a bad practice, similar tocatching all exceptions in java withcatch(Exception)
let x: Result<i32, &str> = Ok(3);match x { Ok(_) => println!("ok"), Err(_) => panic!("err"),}Checks for wildcard enum matches for a single variant.
New enum variants added by library updates can be missed.
Suggested replacements may not use correct path to enumif it’s not present in the current scope.
match x { Foo::A => {}, Foo::B => {}, _ => {},}Use instead:
match x { Foo::A => {}, Foo::B => {}, Foo::C => {},}Checks for iteration that may be infinite.
While there may be places where this is acceptable(e.g., in event streams), in most cases this is simply an error.
The code may have a condition to stop iteration, butthis lint is not clever enough to analyze it.
let infinite_iter = 0..;[0..].iter().zip(infinite_iter.take_while(|x| *x > 5));Checks for usage ofstd::mem::forget(t) wheret isDrop or has a field that implementsDrop.
std::mem::forget(t) preventst from running its destructor, possibly causing leaks.It is not possible to detect all means of creating leaks, but it may be desirable toprohibit the simple ones.
mem::forget(Rc::new(55))Checks formem::replace() on anOption withNone.
Option already has the methodtake() fortaking its current value (Some(..) or None) and replacing it withNone.
use std::mem;let mut an_option = Some(0);let replaced = mem::replace(&mut an_option, None);Is better expressed with:
let mut an_option = Some(0);let taken = an_option.take();Checks formem::replace() on anOption withSome(…).
Option already has the methodreplace() fortaking its current value (Some(…) or None) and replacing it withSome(…).
let mut an_option = Some(0);let replaced = std::mem::replace(&mut an_option, Some(1));Is better expressed with:
let mut an_option = Some(0);let taken = an_option.replace(1);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks forstd::mem::replace on a value of typeT withT::default().
std::mem module already has the methodtake totake the current value and replace it with the default value of that type.
let mut text = String::from("foo");let replaced = std::mem::replace(&mut text, String::default());Is better expressed with:
let mut text = String::from("foo");let taken = std::mem::take(&mut text);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks formem::replace(&mut _, mem::uninitialized())andmem::replace(&mut _, mem::zeroed()).
This will lead to undefined behavior even if thevalue is overwritten later, because the uninitialized value may beobserved in the case of a panic.
use std::mem;#[allow(deprecated, invalid_value)]fn myfunc (v: &mut Vec<i32>) { let taken_v = unsafe { mem::replace(v, mem::uninitialized()) }; let new_v = may_panic(taken_v); // undefined behavior on panic mem::forget(mem::replace(v, new_v));}Thetake_mut crate offers a sound solution,at the cost of either lazily creating a replacement value or abortingon panic, to ensure that the uninitialized value cannot be observed.
Checks for identifiers which consist of a single character (or fewer than the configured threshold).
Note: This lint can be very noisy when enabled; it may be desirable to only enable ittemporarily.
To improve readability by requiring that every variable has a name more specific than a single letter can be.
for m in movies { let title = m.t;}Use instead:
for movie in movies { let title = movie.title;}Trait implementations which use the same function or parameter name as the trait declaration willnot be warned about, even if the name is below the configured limit.
allowed-idents-below-min-chars: Allowed names below the minimum allowed characters. The value".." can be used as part ofthe list to indicate, that the configured values should be appended to the defaultconfiguration of Clippy. By default, any configuration will replace the default value.
(default:["i", "j", "x", "y", "z", "w", "n"])
min-ident-chars-threshold: Minimum chars an ident can have, anything below or equal to this will be linted.
(default:1)
Checks for expressions wherestd::cmp::min andmax areused to clamp values, but switched so that the result is constant.
This is in all probability not the intended outcome. Atthe least it hurts readability of the code.
min(0, max(100, x))// orx.max(100).min(0)It will always be equal to0. Probably the author meant to clamp the valuebetween 0 and 100, but has erroneously swappedmin andmax.
Nothing. This lint has been deprecated
Split intoclippy::cast_ptr_alignment andclippy::transmute_ptr_to_ptr.
Checks for type parameters which are positioned inconsistently betweena type definition and impl block. Specifically, a parameter in an implblock which has the same name as a parameter in the type def, but is ina different place.
Type parameters are determined by their position rather than name.Naming type parameters inconsistently may cause you to refer to thewrong type parameter.
This lint only applies to impl blocks with simple generic params, e.g.A. If there is anything more complicated, such as a tuple, it will beignored.
struct Foo<A, B> { x: A, y: B,}// inside the impl, B refers to Foo::Aimpl<B, A> Foo<B, A> {}Use instead:
struct Foo<A, B> { x: A, y: B,}impl<A, B> Foo<A, B> {}Checks for getter methods that return a field that doesn’t correspondto the name of the method, when there is a field’s whose name matches that of the method.
It is most likely that such a method is a bug caused by a typo or by copy-pasting.
struct A { a: String, b: String,}impl A { fn a(&self) -> &str{ &self.b }}Use instead:
struct A { a: String, b: String,}impl A { fn a(&self) -> &str{ &self.a }}Checks fora op= a op b ora op= b op a patterns.
Most likely these are bugs where one meant to writea op= b.
Clippy cannot know for sure ifa op= a op b should havebeena = a op a op b ora = a op b/a op= b. Therefore, it suggests both.Ifa op= a op b is really the correct behavior it should bewritten asa = a op a op b as it’s less confusing.
let mut a = 5;let b = 2;// ...a += a + b;Checks assertions without a custom panic message.
Without a good custom message, it’d be hard to understand what went wrong when the assertion fails.A good custom message should be more about why the failure of the assertion is problematicand not what is failed because the assertion already conveys that.
Although the same reasoning applies to testing functions, this lint ignores them as they would be too noisy.Also, in most cases understanding the test failure would be easiercompared to understanding a complex invariant distributed around the codebase.
This lint cannot check the quality of the custom panic messages.Hence, you can suppress this lint simply by adding placeholder messageslike “assertion failed”. However, we recommend coming up with good messagesthat provide useful information instead of placeholder messages thatdon’t provide any extra information.
fn call(service: Service) { assert!(service.ready);}Use instead:
fn call(service: Service) { assert!(service.ready, "`service.poll_ready()` must be called first to ensure that service is ready to receive requests");}Checks for repeated slice indexing without asserting beforehand that the lengthis greater than the largest index used to index into the slice.
In the general case where the compiler does not have a lot of informationabout the length of a slice, indexing it repeatedly will generate a bounds checkfor every single index.
Asserting that the length of the slice is at least as large as the largest valueto index beforehand gives the compiler enough information to elide the bounds checks,effectively reducing the number of bounds checks from however many timesthe slice was indexed to just one (the assert).
False positives. It is, in general, very difficult to predict how wellthe optimizer will be able to elide bounds checks and it very much depends onthe surrounding code. For example, indexing into the slice yielded by theslice::chunks_exactiterator will likely have all of the bounds checks elided even without an assertif thechunk_size is a constant.
Asserts are not tracked across function calls. Asserting the length of a slicein a different function likely gives the optimizer enough informationabout the length of a slice, but this lint will not detect that.
fn sum(v: &[u8]) -> u8 { // 4 bounds checks v[0] + v[1] + v[2] + v[3]}Use instead:
fn sum(v: &[u8]) -> u8 { assert!(v.len() > 3); // no bounds checks v[0] + v[1] + v[2] + v[3]}Suggests the use ofconst in functions and methods where possible.
Not having the function const prevents callers of the function from being const as well.
Const functions are currently still being worked on, with some features only being availableon nightly. This lint does not consider all edge cases currently and the suggestions may beincorrect if you are using this lint on stable.
Also, the lint only runs one pass over the code. Consider these two non-const functions:
fn a() -> i32 { 0}fn b() -> i32 { a()}When running Clippy, the lint will only suggest to makea const, becauseb at this timecan’t be const as it calls a non-const function. Makinga const and running Clippy again,will suggest to makeb const, too.
If you are marking a public function withconst, removing it again will break API compatibility.
fn new() -> Self { Self { random_number: 42 }}Could be a const fn:
const fn new() -> Self { Self { random_number: 42 }}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Suggests to useconst inthread_local! macro if possible.
Thethread_local! macro wraps static declarations and makes them thread-local.It supports using aconst keyword that may be used for declarations that canbe evaluated as a constant expression. This can enable a more efficient threadlocal implementation that can avoid lazy initialization. For types that do notneed to be dropped, this can enable an even more efficient implementation thatdoes not need to track any additional state.
https://doc.rust-lang.org/std/macro.thread_local.html
thread_local! { static BUF: String = String::new();}Use instead:
thread_local! { static BUF: String = const { String::new() };}Warns if there is missing documentation for any private documentable item.
Doc is good.rustc has aMISSING_DOCSallowed-by-default lint forpublic members, but has no way to enforce documentation of private items.This lint fixes that.
missing-docs-allow-unused: Whether to allow fields starting with an underscore to skip documentation requirements
(default:false)
missing-docs-in-crate-items: Whether toonly check for missing documentation in items visible within the currentcrate. For example,pub(crate) items.
(default:false)
Checks for imports that do not rename the item as specifiedin theenforced-import-renames config option.
Note: Even though this lint is warn-by-default, it will only trigger ifimport renames are defined in theclippy.toml file.
Consistency is important; if a project has defined import renames, then they should befollowed. More practically, some item names are too vague outside of their defining scope,in which case this can enforce a more meaningful naming.
An example clippy.toml configuration:
enforced-import-renames = [ { path = "serde_json::Value", rename = "JsonValue" },]use serde_json::Value;Use instead:
use serde_json::Value as JsonValue;enforced-import-renames: The list of imports to always rename, a fully qualified path followed by the rename.
(default:[])
Checks the doc comments of publicly visible functions thatreturn aResult type and warns if there is no# Errors section.
Documenting the type of errors that can be returned from afunction can help callers write code to handle the errors appropriately.
Since the following function returns aResult it has an# Errors section inits doc comment:
/// # Errors////// Will return `Err` if `filename` does not exist or the user does not have/// permission to read it.pub fn read(filename: String) -> io::Result<String> { unimplemented!();}check-private-items: Whether to also run the listed lints on private items.
(default:false)
Checks for manualcore::fmt::Debug implementations that do not use all fields.
A common mistake is to forget to update manualDebug implementations when adding a new fieldto a struct or a new variant to an enum.
At the same time, it also acts as a style lint to suggest usingcore::fmt::DebugStruct::finish_non_exhaustivefor the times when the user intentionally wants to leave out certain fields (e.g. to hide implementation details).
This lint works based on theDebugStruct helper types provided by theFormatter,so this won’t detectDebug impls that use thewrite! macro.Oftentimes there is more logic to aDebug impl if it useswrite! macro, so it triesto be on the conservative side and not lint in those cases in an attempt to prevent false positives.
This lint also does not look through function calls, so calling a function does not consider fieldsused inside of that function as used by theDebug impl.
Lastly, it also ignores tuple structs as theirDebugTuple formatter does not have afinish_non_exhaustivemethod, as well as enums because their exhaustiveness is already checked by the compiler when matching on the enum,making it much less likely to accidentally forget to update theDebug impl when adding a new variant.
use std::fmt;struct Foo { data: String, // implementation detail hidden_data: i32}impl fmt::Debug for Foo { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("Foo") .field("data", &self.data) .finish() }}Use instead:
use std::fmt;struct Foo { data: String, // implementation detail hidden_data: i32}impl fmt::Debug for Foo { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("Foo") .field("data", &self.data) .finish_non_exhaustive() }}It lints if an exported function, method, trait method with default impl,or trait method impl is not#[inline].
When a function is not marked#[inline], it is nota “small” candidate for automatic inlining, and LTO is not in use, then it is notpossible for the function to be inlined into the code of any crate other than the one inwhich it is defined. Depending on the role of the function and the relationship of the crates,this could significantly reduce performance.
Certain types of crates might intend for most of the methods in their public API to be ableto be inlined across crates even when LTO is disabled.This lint allows those crates to require all exported methods to be#[inline] by default, andthen opt out for specific methods where this might not make sense.
pub fn foo() {} // missing #[inline]fn ok() {} // ok#[inline] pub fn bar() {} // ok#[inline(always)] pub fn baz() {} // okpub trait Bar { fn bar(); // ok fn def_bar() {} // missing #[inline]}struct Baz;impl Baz { fn private() {} // ok}impl Bar for Baz { fn bar() {} // ok - Baz is not exported}pub struct PubBaz;impl PubBaz { fn private() {} // ok pub fn not_private() {} // missing #[inline]}impl Bar for PubBaz { fn bar() {} // missing #[inline] fn def_bar() {} // missing #[inline]}Checks the doc comments of publicly visible functions thatmay panic and warns if there is no# Panics section.
Documenting the scenarios in which panicking occurscan help callers who do not want to panic to avoid those situations.
Since the following function may panic it has a# Panics section inits doc comment:
/// # Panics////// Will panic if y is 0pub fn divide_by(x: i32, y: i32) -> i32 { if y == 0 { panic!("Cannot divide by 0") } else { x / y }}Individual panics within a function can be ignored with#[expect] or#[allow]:
pub fn will_not_panic(x: usize) { #[expect(clippy::missing_panics_doc, reason = "infallible")] let y = NonZeroUsize::new(1).unwrap(); // If any panics are added in the future the lint will still catch them}check-private-items: Whether to also run the listed lints on private items.
(default:false)
Checks for the doc comments of publicly visibleunsafe functions and warns if there is no# Safety section.
Unsafe functions should document their safetypreconditions, so that users can be sure they are using them safely.
/// This function should really be documentedpub unsafe fn start_apocalypse(u: &mut Universe) { unimplemented!();}At least write a line about safety:
/// # Safety////// This function should not be called before the horsemen are ready.pub unsafe fn start_apocalypse(u: &mut Universe) { unimplemented!();}check-private-items: Whether to also run the listed lints on private items.
(default:false)
Checks for empty spin loops
The loop body should have something likethread::park() or at leaststd::hint::spin_loop() to avoid needlessly burning cycles and conserveenergy. Perhaps even better use an actual lock, if possible.
This lint doesn’t currently trigger onwhile let orloop { match .. { .. } } loops, which would be considered idiomatic incombination with e.g.AtomicBool::compare_exchange_weak.
use core::sync::atomic::{AtomicBool, Ordering};let b = AtomicBool::new(true);// give a ref to `b` to another thread,wait for it to become falsewhile b.load(Ordering::Acquire) {};Use instead:
while b.load(Ordering::Acquire) { std::hint::spin_loop()}Checks if a provided method is used implicitly by a traitimplementation.
To ensure that a certain implementation implements every method; for example,a wrapper type where every method should delegate to the corresponding method ofthe inner type’s implementation.
This lint should typically be enabled on a specific traitimpl itemrather than globally.
trait Trait { fn required(); fn provided() {}}#[warn(clippy::missing_trait_methods)]impl Trait for Type { fn required() { /* ... */ }}Use instead:
trait Trait { fn required(); fn provided() {}}#[warn(clippy::missing_trait_methods)]impl Trait for Type { fn required() { /* ... */ } fn provided() { /* ... */ }}Checks if transmute calls have all generics specified.
If not, one or more unexpected types could be used duringtransmute(), potentially leadingto Undefined Behavior or other problems.
This is particularly dangerous in case a seemingly innocent/unrelated change causes typeinference to result in a different type. For example, iftransmute() is the tailexpression of anif-branch, and theelse-branch type changes, the compiler may silentlyinfer a different type to be returned bytransmute(). That is because the compiler isfree to change the inference of a type as long as that inference is technically correct,regardless of the programmer’s unknown expectation.
Both type-parameters, the input- and the output-type, to anytransmute() shouldbe given explicitly: Setting the input-type explicitly avoids confusion about what theargument’s type actually is. Setting the output-type explicitly avoids type-inferenceto infer a technically correct yet unexpected type.
let mut x: i32 = 0;// Avoid "naked" calls to `transmute()`!x = std::mem::transmute([1u16, 2u16]);// `first_answers` is intended to transmute a slice of bool to a slice of u8.// But the programmer forgot to index the first element of the outer slice,// so we are actually transmuting from "pointers to slices" instead of// transmuting from "a slice of bool", causing a nonsensical result.let the_answers: &[&[bool]] = &[&[true, false, true]];let first_answers: &[u8] = std::mem::transmute(the_answers);Use instead:
let x = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]);// The explicit type parameters on `transmute()` makes the intention clear,// and cause a type-error if the actual types don't match our expectation.let the_answers: &[&[bool]] = &[&[true, false, true]];let first_answers: &[u8] = std::mem::transmute::<&[bool], &[u8]>(the_answers[0]);Warns for mistyped suffix in literals
This is most probably a typo
_127 since that is a valid grouping for decimal and octal numbers`2_32` => `2_i32``250_8 => `250_u8`Checks for items that have the same kind of attributes with mixed styles (inner/outer).
Having both style of said attributes makes it more complicated to read code.
This lint currently has false-negatives when mixing same attributesbut they have different path symbols, for example:
#[custom_attribute]pub fn foo() { #![my_crate::custom_attribute]}#[cfg(linux)]pub fn foo() { #![cfg(windows)]}Use instead:
#[cfg(linux)]#[cfg(windows)]pub fn foo() {}Warns on hexadecimal literals with mixed-case letterdigits.
It looks confusing.
0x1a9BAcDUse instead:
0x1A9BACDChecks for a read and a write to the same variable wherewhether the read occurs before or after the write depends on the evaluationorder of sub-expressions.
While [the evaluation order of sub-expressions] is fully specified in Rust,it still may be confusing to read an expression where the evaluation orderaffects its behavior.
Code which intentionally depends on the evaluationorder, or which is correct for any evaluation order.
let mut x = 0;let a = { x = 1; 1} + x;// Unclear whether a is 1 or 2.Use instead:
let tmp = { x = 1; 1};let a = tmp + x;Checks that module layout uses only self named module files; bansmod.rs files.
Having multiple module layout styles in a project can be confusing.
src/ stuff/ stuff_files.rs mod.rs lib.rsUse instead:
src/ stuff/ stuff_files.rs stuff.rs lib.rsChecks for modules that have the same name as theirparent module
A typical beginner mistake is to havemod foo; andagainmod foo { .. } infoo.rs.The expectation is that items inside the innermod foo { .. } are thenavailablethroughfoo::x, but they are only available throughfoo::foo::x.If this is done on purpose, it would be better to choose a morerepresentative module name.
// lib.rsmod foo;// foo.rsmod foo { ...}allow-private-module-inception: Whether to allow module inception if it’s not public.
(default:false)
Detects public item names that are prefixed or suffixed by thecontaining public module’s name.
It requires the user to type the module name twice in each usage,especially if they choose to import the module rather than its contents.
Lack of such repetition is also the style used in the Rust standard library;e.g.io::Error andfmt::Error rather thanio::IoError andfmt::FmtError;andarray::from_ref rather thanarray::array_from_ref.
Glob re-exports are ignored; e.g. this will not warn even though it should:
pub mod foo { mod iteration { pub struct FooIter {} } pub use iteration::*; // creates the path `foo::FooIter`}mod cake { struct BlackForestCake;}Use instead:
mod cake { struct BlackForest;}allow-exact-repetitions: Whether an item should be allowed to have the same name as its containing module
(default:true)
allowed-prefixes: List of prefixes to allow when determining whether an item’s name ends with the module’s name.If the rest of an item’s name is an allowed prefix (e.g. itemToFoo orto_foo in modulefoo),then don’t emit a warning.
allowed-prefixes = [ "to", "from" ]By default, the following prefixes are allowed:to,as,into,from,try_into andtry_from
PascalCase variant is included automatically for each snake_case variant (e.g. iftry_into is included,TryInto will also be included)
Use".." as part of the list to indicate that the configured values should be appended to thedefault configuration of Clippy. By default, any configuration will replace the default value
(default:["to", "as", "into", "from", "try_into", "try_from"])
Checks for modulo arithmetic.
The results of modulo (%) operation might differdepending on the language, when negative numbers are involved.If you interop with different languages it might be beneficialto double check all places that use modulo arithmetic.
For example, in Rust17 % -3 = 2, but in Python17 % -3 = -1.
let x = -17 % 3;allow-comparison-to-zero: Don’t lint when comparing the result of a modulo operation to zero.
(default:true)
Checks for getting the remainder of integer division by one or minusone.
The result for a divisor of one can only ever be zero; forminus one it can cause panic/overflow (if the left operand is the minimal value ofthe respective integer type) or results in zero. No one will write such codedeliberately, unless trying to win an Underhanded Rust Contest. Even for thatcontest, it’s probably a bad idea. Use something more underhanded.
let a = x % 1;let a = x % -1;Checks for nested assignments.
While this is in most cases already a type mismatch,the result of an assignment being() can throw off people coming from languages like python or C,where such assignments return a copy of the assigned value.
a = b = 42;Use instead:
b = 42;a = b;Check if a generic is defined both in the bound predicate and in thewhere clause.
It can be confusing for developers when seeing bounds for a generic in multiple places.
fn ty<F: std::fmt::Debug>(a: F)where F: Sized,{}Use instead:
fn ty<F>(a: F)where F: Sized + std::fmt::Debug,{}Checks to see if multiple versions of a crate are beingused.
This bloats the size of targets, and can lead toconfusing error messages when structs or traits are used interchangeablybetween different versions of a crate.
Because this can be caused purely by the dependenciesthemselves, it’s not always possible to fix this issue.In those cases, you can allow that specific crate usingtheallowed_duplicate_crates configuration option.
[dependencies]ctrlc = "=3.1.0"ansi_term = "=0.11.0"allowed-duplicate-crates: A list of crate names to allow duplicates of
(default:[])
Checks for multiple inherent implementations of a struct
The config option controls the scope in which multiple inherentimpl blocks for the samestruct are linted, allowing values ofmodule (only within the same module),file(within the same file), orcrate (anywhere in the crate, default).
Splitting the implementation of a type makes the code harder to navigate.
struct X;impl X { fn one() {}}impl X { fn other() {}}Could be written:
struct X;impl X { fn one() {} fn other() {}}inherent-impl-lint-scope: Sets the scope (“crate”, “file”, or “module”) in which duplicate inherentimpl blocks for the same type are linted.
(default:"crate")
Checks forunsafe blocks that contain more than one unsafe operation.
Combined withundocumented_unsafe_blocks,this lint ensures that each unsafe operation must be independently justified.Combined withunused_unsafe, this lint also ensureselimination of unnecessary unsafe blocks through refactoring.
/// Reads a `char` from the given pointer.////// # Safety////// `ptr` must point to four consecutive, initialized bytes which/// form a valid `char` when interpreted in the native byte order.fn read_char(ptr: *const u8) -> char { // SAFETY: The caller has guaranteed that the value pointed // to by `bytes` is a valid `char`. unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) }}Use instead:
/// Reads a `char` from the given pointer.////// # Safety////// - `ptr` must be 4-byte aligned, point to four consecutive/// initialized bytes, and be valid for reads of 4 bytes./// - The bytes pointed to by `ptr` must represent a valid/// `char` when interpreted in the native byte order.fn read_char(ptr: *const u8) -> char { // SAFETY: `ptr` is 4-byte aligned, points to four consecutive // initialized bytes, and is valid for reads of 4 bytes. let int_value = unsafe { *ptr.cast::<u32>() }; // SAFETY: The caller has guaranteed that the four bytes // pointed to by `bytes` represent a valid `char`. unsafe { char::from_u32_unchecked(int_value) }}Checks for public functions that have no#[must_use] attribute, but return something not already markedmust-use, have no mutable arg and mutate no statics.
Not bad at all, this lint just shows places whereyou could add the attribute.
The lint only checks the arguments for mutabletypes without looking if they are actually changed. On the other hand,it also ignores a broad range of potentially interesting side effects,because we cannot decide whether the programmer intends the function tobe called for the side effect or the result. Expect many falsepositives. At least we don’t lint if the result type is unit or already#[must_use].
// this could be annotated with `#[must_use]`.pub fn id<T>(t: T) -> T { t }Checks for a#[must_use] attribute onunit-returning functions and methods.
Unit values are useless. The attribute is likelya remnant of a refactoring that removed the return type.
#[must_use]fn useless() { }This lint checks for functions that take immutable references and returnmutable ones. This will not trigger if no unsafe code exists as thereare multiple safe functions which will do this transformation
To be on the conservative side, if there’s at least one mutablereference with the output lifetime, this lint will not trigger.
Creating a mutable reference which can be repeatably derived from animmutable reference is unsound as it allows creating multiple livemutable references to the same object.
Thiserror actuallylead to an interim Rust release 1.15.1.
This pattern is used by memory allocators to allow allocating multipleobjects while returning mutable references to each one. So long asdifferent mutable references are returned each time such a function maybe safe.
fn foo(&Foo) -> &mut Bar { .. }Checks for instances ofmut mut references.
This is usually just a typo or a misunderstanding of how references work.
let x = &mut &mut 1;let mut x = &mut 1;let y = &mut x;fn foo(x: &mut &mut u32) {}Use instead
let x = &mut 1;let mut x = &mut 1;let y = &mut *x; // reborrowfn foo(x: &mut u32) {}Checks for&mut Mutex::lock calls
Mutex::lock is less efficient thancallingMutex::get_mut. In addition you also have a staticallyguarantee that the mutex isn’t locked, instead of just a runtimeguarantee.
use std::sync::{Arc, Mutex};let mut value_rc = Arc::new(Mutex::new(42_u8));let value_mutex = Arc::get_mut(&mut value_rc).unwrap();let mut value = value_mutex.lock().unwrap();*value += 1;Use instead:
use std::sync::{Arc, Mutex};let mut value_rc = Arc::new(Mutex::new(42_u8));let value_mutex = Arc::get_mut(&mut value_rc).unwrap();let value = value_mutex.get_mut().unwrap();*value += 1;Checks for loops with a range bound that is a mutable variable.
One might think that modifying the mutable variable changes the loop bounds. It doesn’t.
False positive when mutation is followed by abreak, but thebreak is not immediatelyafter the mutation:
let mut x = 5;for _ in 0..x { x += 1; // x is a range bound that is mutated ..; // some other expression break; // leaves the loop, so mutation is not an issue}False positive on nested loops (#6072)
let mut foo = 42;for i in 0..foo { foo -= 1; println!("{i}"); // prints numbers from 0 to 41, not 0 to 21}Checks for sets/maps with mutable key types.
All ofHashMap,HashSet,BTreeMap andBtreeSet rely on either the hash or the order of keys be unchanging,so having types with interior mutability is a bad idea.
It’s correct to use a struct that contains interior mutability as a key when itsimplementation ofHash orOrd doesn’t access any of the interior mutable types.However, this lint is unable to recognize this, so it will often cause false positives inthese cases.
This lint does not follow raw pointers (*const T or*mut T) asHash andOrdapply only to theaddress of the contained value. This can cause false negatives forcustom collections that use raw pointers internally.
use std::cmp::{PartialEq, Eq};use std::collections::HashSet;use std::hash::{Hash, Hasher};use std::sync::atomic::AtomicUsize;struct Bad(AtomicUsize);impl PartialEq for Bad { fn eq(&self, rhs: &Self) -> bool { .. }}impl Eq for Bad {}impl Hash for Bad { fn hash<H: Hasher>(&self, h: &mut H) { .. }}fn main() { let _: HashSet<Bad> = HashSet::new();}ignore-interior-mutability: A list of paths to types that should be treated as if they do not contain interior mutability
(default:["bytes::Bytes"])
Checks for usage ofMutex<X> where an atomic will do.
Using a mutex just to make access to a plain bool orreference sequential is shooting flies with cannons.std::sync::atomic::AtomicBool andstd::sync::atomic::AtomicPtr are leaner andfaster.
On the other hand,Mutexes are, in general, easier toverify correctness. An atomic does not behave the same asan equivalent mutex. Seethis issue’scommentary for more details.
Mutex is used together withCondvar.let x = Mutex::new(&y);Use instead:
let x = AtomicBool::new(y);Checks for usage ofMutex<X> whereX is an integraltype.
Using a mutex just to make access to a plain integersequential isshooting flies with cannons.std::sync::atomic::AtomicUsize is leaner and faster.
On the other hand,Mutexes are, in general, easier toverify correctness. An atomic does not behave the same asan equivalent mutex. Seethis issue’scommentary for more details.
Mutex is used together withCondvar.AtomicU64 instead ofMutex<u64>, butAtomicU64 is not available on some 32-bit platforms.let x = Mutex::new(0usize);Use instead:
let x = AtomicUsize::new(0usize);Checks for naive byte counts
Thebytecountcrate has methods to count your bytes faster, especially for large slices.
If you have predominantly small slices, thebytecount::count(..) method may actually be slower. However, if you canensure that less than 2³²-1 matches arise, thenaive_count_32(..) can befaster in those cases.
let count = vec.iter().filter(|x| **x == 0u8).count();Use instead:
let count = bytecount::count(&vec, 0u8);The lint checks forself in fn parameters thatspecify theSelf-type explicitly
Increases the amount and decreases the readability of code
enum ValType { I32, I64, F32, F64,}impl ValType { pub fn bytes(self: Self) -> usize { match self { Self::I32 | Self::F32 => 4, Self::I64 | Self::F64 => 8, } }}Could be rewritten as
enum ValType { I32, I64, F32, F64,}impl ValType { pub fn bytes(self) -> usize { match self { Self::I32 | Self::F32 => 4, Self::I64 | Self::F64 => 8, } }}It detects useless calls tostr::as_bytes() before callinglen() oris_empty().
Thelen() andis_empty() methods are also directly available on strings, and theyreturn identical results. In particular,len() on a string returns the number ofbytes.
let len = "some string".as_bytes().len();let b = "some string".as_bytes().is_empty();Use instead:
let len = "some string".len();let b = "some string".is_empty();Checks for usage of bitwise and/or operators between booleans, where performance may be improved by usinga lazy and.
The bitwise operators do not support short-circuiting, so it may hinder code performance.Additionally, boolean logic “masked” as bitwise logic is not caught by lints likeunnecessary_fold
This lint evaluates only when the right side is determined to have no side effects. At this time, thatdetermination is quite conservative.
let (x,y) = (true, false);if x & !y {} // where both x and y are booleansUse instead:
let (x,y) = (true, false);if x && !y {}Checks for expressions of the formif c { true } else { false } (or vice versa) and suggests using the condition directly.
Redundant code.
Maybe false positives: Sometimes, the two branches arepainstakingly documented (which we, of course, do not detect), so theymayhave some value. Even then, the documentation can be rewritten to match theshorter code.
if x { false} else { true}Use instead:
!xChecks for expressions of the formif c { x = true } else { x = false }(or vice versa) and suggest assigning the variable directly from thecondition.
Redundant code.
if must_keep(x, y) { skip = false;} else { skip = true;}Use instead:
skip = !must_keep(x, y);Checks for address of operations (&) that are going tobe dereferenced immediately by the compiler.
Suggests that the receiver of the expression borrowsthe expression.
The lint cannot tell when the implementation of a traitfor&T andT do different things. Removing a borrowin such a case can change the semantics of the code.
fn fun(_a: &i32) {}let x: &i32 = &&&&&&5;fun(&x);Use instead:
let x: &i32 = &5;fun(x);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for bindings that needlessly destructure a reference and borrow the innervalue with&ref.
This pattern has no effect in almost all cases.
let mut v = Vec::<String>::new();v.iter_mut().filter(|&ref a| a.is_empty());if let &[ref first, ref second] = v.as_slice() {}Use instead:
let mut v = Vec::<String>::new();v.iter_mut().filter(|a| a.is_empty());if let [first, second] = v.as_slice() {}Checks for borrow operations (&) that are used as a generic argument to afunction when the borrowed value could be used.
Suggests that the receiver of the expression borrowsthe expression.
The lint cannot tell when the implementation of a traitfor&T andT do different things. Removing a borrowin such a case can change the semantics of the code.
fn f(_: impl AsRef<str>) {}let x = "foo";f(&x);Use instead:
fn f(_: impl AsRef<str>) {}let x = "foo";f(x);Checks if an iterator is used to check if a string is ascii.
Thestr type already implements theis_ascii method.
"foo".chars().all(|c| c.is_ascii());Use instead:
"foo".is_ascii();Checks for functions collecting an iterator when collectis not needed.
collect causes the allocation of a new data structure,when this allocation may not be needed.
let len = iterator.collect::<Vec<_>>().len();Use instead:
let len = iterator.count();The lint checks forif-statements appearing in loopsthat contain acontinue statement in either their main blocks or theirelse-blocks, when omitting theelse-block possibly with somerearrangement of code can make the code easier to understand.The lint also checks if the last statement in the loop is acontinue
Having explicitelse blocks forif statementscontainingcontinue in their THEN branch adds unnecessary branching andnesting to the code. Having an else block containing justcontinue canalso be better written by grouping the statements following the wholeifstatement within the THEN block and omitting the else block completely.
while condition() { update_condition(); if x { // ... } else { continue; } println!("Hello, world");}Could be rewritten as
while condition() { update_condition(); if x { // ... println!("Hello, world"); }}As another example, the following code
loop { if waiting() { continue; } else { // Do something useful } # break;}Could be rewritten as
loop { if waiting() { continue; } // Do something useful # break;}fn foo() -> ErrorKind { ErrorKind::NotFound }for _ in 0..10 { match foo() { ErrorKind::NotFound => { eprintln!("not found"); continue } ErrorKind::TimedOut => { eprintln!("timeout"); continue } _ => { eprintln!("other error"); continue } }}Could be rewritten as
fn foo() -> ErrorKind { ErrorKind::NotFound }for _ in 0..10 { match foo() { ErrorKind::NotFound => { eprintln!("not found"); } ErrorKind::TimedOut => { eprintln!("timeout"); } _ => { eprintln!("other error"); } }}Checks forfn main() { .. } in doctests
The test can be shorter (and likely more readable)if thefn main() is left implicit.
/// An example of a doctest with a `main()` function////// # Examples////// ```/// fn main() {/// // this needs not be in an `fn`/// }/// ```fn needless_main() { unimplemented!();}Checks for emptyelse branches.
An empty else branch does nothing and can be removed.
if check() { println!("Check successful!");} else {}Use instead:
if check() { println!("Check successful!");}Checks for usage offor_each that would be more simply written as afor loop.
for_each may be used after applying iterator transformers likefilter for better readability and performance. It may also be used to fit a simpleoperation on one line.But when none of these apply, a simplefor loop is more idiomatic.
let v = vec![0, 1, 2];v.iter().for_each(|elem| { println!("{elem}");})Use instead:
let v = vec![0, 1, 2];for elem in &v { println!("{elem}");}When doing things such as:
let v = vec![0, 1, 2];v.iter().for_each(|elem| unsafe { libc::printf(c"%d\n".as_ptr(), elem);});This lint will not trigger.
Checks for emptyif branches with no else branch.
It can be entirely omitted, and often the condition too.
This will usually only suggest to remove theif statement, not the condition. Other lintssuch asno_effect will take care of removing the condition if it’s unnecessary.
if really_expensive_condition(&i) {}if really_expensive_condition_with_side_effects(&mut i) {}Use instead:
// <omitted>really_expensive_condition_with_side_effects(&mut i);Checks for late initializations that can be replaced by alet statementwith an initializer.
Assigning in thelet statement is less repetitive.
let a;a = 1;let b;match 3 { 0 => b = "zero", 1 => b = "one", _ => b = "many",}let c;if true { c = 1;} else { c = -1;}Use instead:
let a = 1;let b = match 3 { 0 => "zero", 1 => "one", _ => "many",};let c = if true { 1} else { -1};Checks for lifetime annotations which can be removed byrelying on lifetime elision.
The additional lifetimes make the code look morecomplicated, while there is nothing out of the ordinary going on. Removingthem leads to more readable code.
This lint ignores functions withwhere clauses that referencelifetimes to prevent false positives.
// Unnecessary lifetime annotationsfn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 { x}Use instead:
fn elided(x: &u8, y: u8) -> &u8 { x}Checks for unnecessarymatch or match-likeif let returns forOption andResultwhen function signatures are the same.
Thismatch block does nothing and might not be what the coder intended.
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 }}Could be replaced as
fn foo() -> Result<(), i32> { result}fn bar() -> Option<i32> { option}Lints?Sized bounds applied to type parameters that cannot be unsized
The?Sized bound is misleading because it cannot be satisfied by anunsized type
// `T` cannot be unsized because `Clone` requires it to be `Sized`fn f<T: Clone + ?Sized>(t: &T) {}Use instead:
fn f<T: Clone>(t: &T) {}// or choose alternative bounds for `T` so that it can be unsizedChecks for no-op uses ofOption::{as_deref, as_deref_mut},for example,Option<&T>::as_deref() returns the same type.
Redundant code and improving readability.
let a = Some(&1);let b = a.as_deref(); // goes from Option<&i32> to Option<&i32>Use instead:
let a = Some(&1);let b = a;Checks for callingtake function afteras_ref.
Redundant code.take writesNone to its argument.In this case the modification is useless as it’s a temporary that cannot be read from afterwards.
let x = Some(3);x.as_ref().take();Use instead:
let x = Some(3);x.as_ref();The lint checks for parenthesis on literals in range statements that aresuperfluous.
Having superfluous parenthesis makes the code less readableoverhead when reading.
for i in (0)..10 { println!("{i}");}Use instead:
for i in 0..10 { println!("{i}");}Check if a&mut function argument is actually used mutably.
Be careful if the function is publicly reexported as it would break compatibility withusers of this function, when the users pass this function as an argument.
Lessmut means less fights with the borrow checker. It can also lead to moreopportunities for parallelization.
fn foo(y: &mut i32) -> i32 { 12 + *y}Use instead:
fn foo(y: &i32) -> i32 { 12 + *y}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Checks for functions taking arguments by value, but notconsuming them in itsbody.
Taking arguments by reference is more flexible and cansometimes avoidunnecessary allocations.
Borrow trait, for example), depending on how the function is used.fn foo(v: Vec<i32>) { assert_eq!(v.len(), 42);}should be
fn foo(v: &[i32]) { assert_eq!(v.len(), 42);}Checks for usage ofpub(self) andpub(in self).
It’s unnecessary, omitting thepub entirely will give the same results.
pub(self) type OptBox<T> = Option<Box<T>>;Use instead:
type OptBox<T> = Option<Box<T>>;Suggests replacingOk(x?) orSome(x?) withx in return positions where the? operatoris not needed to convert the type ofx.
There’s no reason to use? to short-circuit when execution of the body will end there anyway.
fn f(s: &str) -> Option<usize> { Some(s.find('x')?)}fn g(s: &str) -> Result<usize, ParseIntError> { Ok(s.parse()?)}Use instead:
fn f(s: &str) -> Option<usize> { s.find('x')}fn g(s: &str) -> Result<usize, ParseIntError> { s.parse()}Checks for looping over the range of0..len of somecollection just to get the values by index.
Just iterating the collection itself makes the intentmore clear and is probably faster because it eliminatesthe bounds check that is done when indexing.
let vec = vec!['a', 'b', 'c'];for i in 0..vec.len() { println!("{}", vec[i]);}Use instead:
let vec = vec!['a', 'b', 'c'];for i in vec { println!("{}", i);}Checks for raw string literals with an unnecessary amount of hashes around them.
It’s just unnecessary, and makes it look like there’s more escaping needed than is actuallynecessary.
let r = r###"Hello, "world"!"###;Use instead:
let r = r#"Hello, "world"!"#;allow-one-hash-in-raw-strings: Whether to allowr#""# whenr"" can be used
(default:false)
Checks for raw string literals where a string literal can be used instead.
For consistent style by using simpler string literals whenever possible.
However, there are many cases where using a raw string literal is moreidiomatic than a string literal, so it’s opt-in.
let r = r"Hello, world!";Use instead:
let r = "Hello, world!";Checks for return statements at the end of a block.
Removing thereturn and semicolon will make the codemore rusty.
fn foo(x: usize) -> usize { return x;}simplify to
fn foo(x: usize) -> usize { x}Checks for return statements onErr paired with the? operator.
Thereturn is unnecessary.
Returns may be used to add attributes to the return expression. Returnstatements with attributes are therefore be accepted by this lint.
fn foo(x: usize) -> Result<(), Box<dyn Error>> { if x == 0 { return Err(...)?; } Ok(())}simplify to
fn foo(x: usize) -> Result<(), Box<dyn Error>> { if x == 0 { Err(...)?; } Ok(())}if paired withtry_err, use instead:
fn foo(x: usize) -> Result<(), Box<dyn Error>> { if x == 0 { return Err(...); } Ok(())}Checks for usage ofstr::splitn (orstr::rsplitn) where usingstr::split would be the same.
The functionsplit is simpler and there is no performance difference in these cases, consideringthat both functions return a lazy iterator.
let str = "key=value=add";let _ = str.splitn(3, '=').next().unwrap();Use instead:
let str = "key=value=add";let _ = str.split('=').next().unwrap();Checks for needlessly including a base struct on updatewhen all fields are changed anyway.
This lint is not applied to structs marked withnon_exhaustive.
This will cost resources (because the base has to besomewhere), and make the code less readable.
Point { x: 1, y: 1, z: 1, ..zero_point};Use instead:
// Missing field `z`Point { x: 1, y: 1, ..zero_point};Checks for the usage of negated comparison operators on types which only implementPartialOrd (e.g.,f64).
These operators make it easy to forget that the underlying types actually allow not only threepotential Orderings (Less, Equal, Greater) but also a fourth one (Uncomparable). This isespecially easy to miss if the operator based comparison result is negated.
let a = 1.0;let b = f64::NAN;let not_less_or_equal = !(a <= b);Use instead:
use std::cmp::Ordering;let _not_less_or_equal = match a.partial_cmp(&b) { None | Some(Ordering::Greater) => true, _ => false,};Checks for multiplication by -1 as a form of negation.
It’s more readable to just negate.
let a = x * -1;Use instead:
let a = -x;Checks for negative feature names with prefixno- ornot-
Features are supposed to be additive, and negatively-named features violate it.
[features]default = []no-abc = []not-def = []Use instead:
[features]default = ["abc", "def"]abc = []def = []Checks for loops that will alwaysbreak,return orcontinue an outer loop.
This loop never loops, all it does is obfuscating thecode.
loop { ..; break;}Checks fornew not returning a type that containsSelf.
As a convention,new methods are used to make a newinstance of a type.
In an impl block:
impl Foo { fn new() -> NotAFoo { }}struct Bar(Foo);impl Foo { // Bad. The type name must contain `Self` fn new() -> Bar { }}impl Foo { // Good. Return type contains `Self` fn new() -> Result<Foo, FooError> { }}Or in a trait definition:
pub trait Trait { // Bad. The type name must contain `Self` fn new();}pub trait Trait { // Good. Return type contains `Self` fn new() -> Self;}Checks for public types with apub fn new() -> Self method and noimplementation ofDefault.
The user might expect to be able to useDefault as thetype can be constructed without arguments.
pub struct Foo(Bar);impl Foo { pub fn new() -> Self { Foo(Bar::new()) }}To fix the lint, add aDefault implementation that delegates tonew:
pub struct Foo(Bar);impl Default for Foo { fn default() -> Self { Foo::new() }}Checks for statements which have no effect.
Unlike dead code, these statements are actuallyexecuted. However, as they have no effect, all they do is make the code lessreadable.
0;Checks forreplace statements which have no effect.
It’s either a mistake or confusing.
"1234".replace("12", "12");"1234".replacen("12", "12", 1);Checks for binding to underscore prefixed variable without side-effects.
Unlike dead code, these bindings are actuallyexecuted. However, as they have no effect and shouldn’t be used further on, all theydo is make the code less readable.
let _i_serve_no_purpose = 1;Checks for Rust ABI functions with the#[no_mangle] attribute.
The Rust ABI is not stable, but in many simple cases matchesenough with the C ABI that it is possible to forget to addextern "C" to a function called from C. Changes to theRust ABI can break this at any point.
#[no_mangle] fn example(arg_one: u32, arg_two: usize) {}Use instead:
#[no_mangle] extern "C" fn example(arg_one: u32, arg_two: usize) {}Checks for non-ASCII characters in string and char literals.
Yeah, we know, the 90’s called and wanted their charsetback. Even so, there still are editors and other programs out there thatdon’t work well with Unicode. So if the code is meant to be usedinternationally, on multiple operating systems, or has other portabilityrequirements, activating this lint could be useful.
let x = String::from("€");Use instead:
let x = String::from("\u{20ac}");Checks for non-canonical implementations ofClone whenCopy is already implemented.
If bothClone andCopy are implemented, they must agree. This can done by dereferencingself inClone’s implementation, which will avoid any possibility of the implementationsbecoming out of sync.
#[derive(Eq, PartialEq)]struct A(u32);impl Clone for A { fn clone(&self) -> Self { Self(self.0) }}impl Copy for A {}Use instead:
#[derive(Eq, PartialEq)]struct A(u32);impl Clone for A { fn clone(&self) -> Self { *self }}impl Copy for A {}Checks for non-canonical implementations ofPartialOrd whenOrd is already implemented.
If bothPartialOrd andOrd are implemented, they must agree. This is commonly done bywrapping the result ofcmp inSome forpartial_cmp. Not doing this may silentlyintroduce an error upon refactoring.
Code that calls the.into() method instead will be flagged, despite.into() wrapping itinSome.
#[derive(Eq, PartialEq)]struct A(u32);impl Ord for A { fn cmp(&self, other: &Self) -> Ordering { // ... }}impl PartialOrd for A { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { // ... }}Use instead:
#[derive(Eq, PartialEq)]struct A(u32);impl Ord for A { fn cmp(&self, other: &Self) -> Ordering { // ... }}impl PartialOrd for A { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) // or self.cmp(other).into() }}Checks forany andall combinators incfg with only one condition.
If there is only one condition, no need to wrap it intoany orall combinators.
#[cfg(any(unix))]pub struct Bar;Use instead:
#[cfg(unix)]pub struct Bar;Checks for non-octal values used to set Unix file permissions.
They will be converted into octal, creating potentiallyunintended file permissions.
use std::fs::OpenOptions;use std::os::unix::fs::OpenOptionsExt;let mut options = OpenOptions::new();options.mode(644);Use instead:
use std::fs::OpenOptions;use std::os::unix::fs::OpenOptionsExt;let mut options = OpenOptions::new();options.mode(0o644);This lint warns about aSend implementation for a type thatcontains fields that are not safe to be sent across threads.It tries to detect fields that can cause a soundness issuewhen sent to another thread (e.g.,Rc) while allowing!Send fieldsthat are expected to exist in aSend type, such as raw pointers.
Sending the struct to another thread effectively sends all of its fields,and the fields that do not implementSend can lead to soundness bugssuch as data races when accessed in a threadthat is different from the thread that created it.
See:
This lint relies on heuristics to distinguish types that are actuallyunsafe to be sent across threads and!Send types that are expected toexist inSend type. Its rule can filter out basic cases such asVec<*const T>, but it’s not perfect. Feel free to create an issue ifyou have a suggestion on how this heuristic can be improved.
struct ExampleStruct<T> { rc_is_not_send: Rc<String>, unbounded_generic_field: T,}// This impl is unsound because it allows sending `!Send` types through `ExampleStruct`unsafe impl<T> Send for ExampleStruct<T> {}Use thread-safe types likestd::sync::Arcor specify correct bounds on generic type parameters (T: Send).
enable-raw-pointer-heuristic-for-send: Whether to apply the raw pointer heuristic to determine if a type isSend.
(default:true)
Lints whenonce_cell::sync::Lazy orlazy_static! are used to define a static variable,and suggests replacing such cases withstd::sync::LazyLock instead.
Note: This lint will not trigger in crate withno_std context, or with MSRV < 1.80.0. Italso will not trigger ononce_cell::sync::Lazy usage in crates which use other typesfromonce_cell, such asonce_cell::race::OnceBox.
lazy_static! { static ref FOO: String = "foo".to_uppercase();}static BAR: once_cell::sync::Lazy<String> = once_cell::sync::Lazy::new(|| "BAR".to_lowercase());Use instead:
static FOO: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| "FOO".to_lowercase());static BAR: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| "BAR".to_lowercase());msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for conversions fromNonZero types to regular integer types,and suggests usingNonZero types for the target as well.
Converting fromNonZero types to regular integer types and then back toNonZerotypes is less efficient and loses the type-safety guarantees provided byNonZero types.UsingNonZero types consistently can lead to more optimized code and preventcertain classes of errors related to zero values.
use std::num::{NonZeroU32, NonZeroU64};fn example(x: u64, y: NonZeroU32) { // Bad: Converting NonZeroU32 to u64 unnecessarily let r1 = x / u64::from(y.get()); let r2 = x % u64::from(y.get());}Use instead:
use std::num::{NonZeroU32, NonZeroU64};fn example(x: u64, y: NonZeroU32) { // Good: Preserving the NonZero property let r1 = x / NonZeroU64::from(y); let r2 = x % NonZeroU64::from(y);}Checks for boolean expressions that can be written moreconcisely.
Readability of boolean expressions suffers fromunnecessary duplication.
Ignores short circuiting behavior of|| and&&. Ignores|,& and^.
if a && true {}if !(a == b) {}Use instead:
if a {}if a != b {}Checks for duplicate open options as well as combinationsthat make no sense.
In the best case, the code will be harder to read thannecessary. I don’t know the worst case.
use std::fs::OpenOptions;OpenOptions::new().read(true).truncate(true);Checks that common macros are used with consistent bracing.
Having non-conventional braces on well-stablished macros can be confusingwhen debugging, and they bring incosistencies with the rest of the ecosystem.
vec!{1, 2, 3};Use instead:
vec![1, 2, 3];standard-macro-braces: Enforce the named macros always use the braces specified.AMacroMatcher can be added like so{ name = "macro_name", brace = "(" }. If the macrocould be used with a full path twoMacroMatchers have to be added one with the full pathcrate_name::macro_name and one with just the macro name.
(default:[])
Checks for public functions that dereference raw pointerarguments but are not markedunsafe.
The function should almost definitely be markedunsafe, since for anarbitrary raw pointer, there is no way of telling for sure if it is valid.
In general, this lint shouldnever be disabled unless it is definitely afalse positive (please submit an issue if so) since it breaks Rust’ssoundness guarantees, directly exposing API users to potentially dangerousprogram behavior. This is also true for internal APIs, as it is easy to leakunsoundness.
In Rust, anunsafe {...} block is used to indicate that the code in thatsection has been verified in some way that the compiler can not. For afunction that accepts a raw pointer then accesses the pointer’s data, this isgenerally impossible as the incoming pointer could point anywhere, valid ornot. So, the signature should be markedunsafe fn: this indicates that thefunction’s caller must provide some verification that the arguments it sendsare valid (and then call the function within anunsafe block).
unsafe function which does the dereferencing, the lint won’ttrigger (false negative).fn foo(bar: &[*const u8]) orsome_argument.get_raw_ptr()) (false negative).pub fn foo(x: *const u8) { println!("{}", unsafe { *x });}// this call "looks" safe but will segfault or worse!// foo(invalid_ptr);Use instead:
pub unsafe fn foo(x: *const u8) { println!("{}", unsafe { *x });}// this would cause a compiler error for calling without `unsafe`// foo(invalid_ptr);// sound call if the caller knows the pointer is validunsafe { foo(valid_ptr); }Checks for unnecessary method chains that can be simplified intoif .. else ...
This can be written more clearly withif .. else ..
This lint currently only looks for usages of.{then, then_some}(..).{unwrap_or, unwrap_or_else, unwrap_or_default}(..), but will be expandedto account for similar patterns.
let x = true;x.then_some("a").unwrap_or("b");Use instead:
let x = true;if x { "a" } else { "b" };Checks for\0 escapes in string and byte literals that look like octalcharacter escapes in C.
C and other languages support octal character escapes in strings, wherea backslash is followed by up to three octal digits. For example,\033stands for the ASCII character 27 (ESC). Rust does not support thisnotation, but has the escape code\0 which stands for a nullbyte/character, and any following digits do not form part of the escapesequence. Therefore,\033 is not a compiler error but the result maybe surprising.
The actual meaning can be the intended one.\x00 can be used in thesecases to be unambiguous.
The lint does not trigger for format strings inprint!(),write!()and friends since the string is already preprocessed when Clippy lintscan see it.
let one = "\033[1m Bold? \033[0m"; // \033 intended as escapelet two = "\033\0"; // \033 intended as null-3-3Use instead:
let one = "\x1b[1mWill this be bold?\x1b[0m";let two = "\x0033\x00";Checks for usage ofok().expect(..).
Note: This lint only triggers for code marked compatiblewith versions of the compiler older than Rust 1.82.0.
Because you usually callexpect() on theResultdirectly to get a better error message.
x.ok().expect("why did I do this again?");Use instead:
x.expect("why did I do this again?");Checks for arguments that are only used in recursion with no side-effects.
It could contain a useless calculation and can make function simpler.
The arguments can be involved in calculations and assignments but as long asthe calculations have no side-effects (function calls or mutating dereference)and the assigned variables are also only in recursion, it is useless.
fn f(a: usize, b: usize) -> usize { if a == 0 { 1 } else { f(a - 1, b + 1) }}Use instead:
fn f(a: usize) -> usize { if a == 0 { 1 } else { f(a - 1) }}Too many code paths in the linting code are currently untested and prone to produce falsepositives or are prone to have performance implications.
In some cases, this would not catch all useless arguments.
fn foo(a: usize, b: usize) -> usize { let f = |x| x + 1; if a == 0 { 1 } else { foo(a - 1, f(b)) }}For example, the argumentb is only used in recursion, but the lint would not catch it.
List of some examples that can not be caught:
break relative operationsAlso, when you recurse the function name with path segments, it is not possible to detect.
Checks for arguments to== which have their addresstaken to satisfy a boundand suggests to dereference the other argument instead
It is more idiomatic to dereference the other argument.
&x == yUse instead:
x == *yChecks for usage of.as_ref().cloned() and.as_mut().cloned() onOptions
This can be written more concisely by cloning theOption directly.
fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> { bar.as_ref().cloned()}Use instead:
fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> { bar.clone()}Checks for usage of_.as_ref().map(Deref::deref) or its aliases (such as String::as_str).
Readability, this can be written more concisely as_.as_deref().
opt.as_ref().map(String::as_str)Can be written as
opt.as_deref()msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage ofoption_env!(...).unwrap() andsuggests usage of theenv! macro.
Unwrapping the result ofoption_env! will panicat run-time if the environment variable doesn’t exist, whereasenv!catches it at compile-time.
let _ = option_env!("HOME").unwrap();Is better expressed as:
let _ = env!("HOME");Checks for iterators ofOptions using.filter(Option::is_some).map(Option::unwrap) that maybe replaced with a.flatten() call.
Option is like a collection of 0-1 things, soflattenautomatically does this without suspicious-lookingunwrap calls.
let _ = std::iter::empty::<Option<i32>>().filter(Option::is_some).map(Option::unwrap);Use instead:
let _ = std::iter::empty::<Option<i32>>().flatten();Lints usage ofif let Some(v) = ... { y } else { x } andmatch .. { Some(v) => y, None/_ => x } which are moreidiomatically done withOption::map_or (if the else bit is a pureexpression) orOption::map_or_else (if the else bit is an impureexpression).
Using the dedicated functions of theOption type is clearer andmore concise than anif let expression.
This lint uses a deliberately conservative metric for checking if theinside of either body contains loop control expressionsbreak orcontinue (which cannot be used within closures). If these are found,this lint will not be raised.
let _ = if let Some(foo) = optional { foo} else { 5};let _ = match optional { Some(val) => val + 1, None => 5};let _ = if let Some(foo) = optional { foo} else { let y = do_complicated_function(); y*y};should be
let _ = optional.map_or(5, |foo| foo);let _ = optional.map_or(5, |val| val + 1);let _ = optional.map_or_else(||{ let y = do_complicated_function(); y*y}, |foo| foo);Nothing. This lint has been deprecated
clippy::manual_ok_or covers this case.
Checks for usage of_.map_or(None, _).
Readability, this can be written more concisely as_.and_then(_).
The order of the arguments is not in execution order.
opt.map_or(None, |a| Some(a + 1));Use instead:
opt.and_then(|a| Some(a + 1));Checks for usage ofoption.map(f) where f is a functionor closure that returns the unit type().
Readability, this can be written more clearly withan if let statement
let x: Option<String> = do_stuff();x.map(log_err_msg);x.map(|msg| log_err_msg(format_msg(msg)));The correct use would be:
let x: Option<String> = do_stuff();if let Some(msg) = x { log_err_msg(msg);}if let Some(msg) = x { log_err_msg(format_msg(msg));}Checks for usage ofOption<Option<_>> in function signatures and typedefinitions
Option<_> represents an optional value.Option<Option<_>>represents an optional value which itself wraps an optional. This is logically thesame thing as an optional value but has an unneeded extra level of wrapping.
If you have a case whereSome(Some(_)),Some(None) andNone are distinct cases,consider a customenum instead, with clear names for each case.
fn get_data() -> Option<Option<u32>> { None}Better:
pub enum Contents { Data(Vec<u8>), // Was Some(Some(Vec<u8>)) NotYetFetched, // Was Some(None) None, // Was None}fn get_data() -> Contents { Contents::None}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Checks for calls to.or(foo(..)),.unwrap_or(foo(..)),.or_insert(foo(..)) etc., and suggests to use.or_else(|| foo(..)),.unwrap_or_else(|| foo(..)),.unwrap_or_default() or.or_default()etc. instead.
The function will always be called. This is only bad if it allocates ordoes some non-trivial amount of work.
If the function has side-effects, not calling it will change thesemantic of the program, but you shouldn’t rely on that.
The lint also cannot figure out whether the function you call isactually expensive to call or not.
foo.unwrap_or(String::from("empty"));Use instead:
foo.unwrap_or_else(|| String::from("empty"));msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for.or(…).unwrap() calls to Options and Results.
You should use.unwrap_or(…) instead for clarity.
// Resultlet value = result.or::<Error>(Ok(fallback)).unwrap();// Optionlet value = option.or(Some(fallback)).unwrap();Use instead:
// Resultlet value = result.unwrap_or(fallback);// Optionlet value = option.unwrap_or(fallback);Checks for out of bounds array indexing with a constantindex.
This will always panic at runtime.
let x = [1, 2, 3, 4];x[9];&x[2..9];Use instead:
// Index within boundsx[0];x[3];Checks for boolean expressions that contain terminals thatcan be eliminated.
This is most likely a logic bug.
Ignores short circuiting behavior.
// The `b` is unnecessary, the expression is equivalent to `if a`.if a && b || a { ... }Use instead:
if a {}Detects needlessly ownedCow types.
The borrowed types are usually more flexible, in that e.g. aCow<'_, str> can accept both&str andString whileCow<'_, String> can only accept&String andString. Inparticular,&str is more general, because it allows for stringliterals while&String can only be borrowed from a heap-ownedString).
The lint does not check for usage of the type. There may be externalinterfaces that require the use of an owned type.
At least theCString type also has a different API thanCStr: Theformer has anas_bytes method which the latter callsto_bytes.There is no guarantee that other types won’t gain additional methodsleading to a similar mismatch.
In addition, the lint only checks for the known problematic typesString,Vec<_>,CString,OsString andPathBuf. Custom typesthat implementToOwned will not be detected.
let wrogn: std::borrow::Cow<'_, Vec<u8>>;Use instead:
let right: std::borrow::Cow<'_, [u8]>;avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Checks for usage ofpanic!.
This macro, or panics in general, may be unwanted in production code.
panic!("even with a good reason");allow-panic-in-tests: Whetherpanic should be allowed in test functions or#[cfg(test)]
(default:false)
Checks for usage ofpanic! or assertions in a function whose return type isResult.
For some codebases, it is desirable for functions of type result to return an error instead of crashing. Hence panicking macros should be avoided.
Functions called from a function returning aResult may invoke a panicking macro. This is not checked.
fn result_with_panic() -> Result<bool, String>{ panic!("error");}Use instead:
fn result_without_panic() -> Result<bool, String> { Err(String::from("error"))}Detects C-style underflow/overflow checks.
These checks will, by default, panic in debug builds rather than checkwhether the operation caused an overflow.
if a + b < a { // handle overflow}Use instead:
if a.checked_add(b).is_none() { // handle overflow}Or:
if a.overflowing_add(b).1 { // handle overflow}Checks for calls ofunwrap[_err]() that will always fail.
If panicking is desired, an explicitpanic!() should be used.
This lint only checksif conditions not assignments.So something likelet x: Option<()> = None; x.unwrap(); will not be recognized.
if option.is_none() { do_something_with(option.unwrap())}This code will always panic. The if condition should probably be inverted.
Checks whether some but not all fields of astruct are public.
Either make all fields of a type public, or make none of them public
Most types should either be:
pub struct Color { pub r: u8, pub g: u8, b: u8,}Use instead:
pub struct Color { pub r: u8, pub g: u8, pub b: u8,}Checks for manual re-implementations ofPartialEq::ne.
PartialEq::ne is required to always return thenegated result ofPartialEq::eq, which is exactly what the defaultimplementation does. Therefore, there should never be any need tore-implement it.
struct Foo;impl PartialEq for Foo { fn eq(&self, other: &Foo) -> bool { true } fn ne(&self, other: &Foo) -> bool { !(self == other) }}Checks for binary comparisons to a literalOption::None.
A programmer checking if somefoo isNone via a comparisonfoo == Noneis usually inspired from other programming languages (e.g.foo is Nonein Python).Checking if a value of typeOption<T> is (not) equal toNone in thatway relies onT: PartialEq to do the comparison, which is unneeded.
fn foo(f: Option<u32>) -> &'static str { if f != None { "yay" } else { "nay" }}Use instead:
fn foo(f: Option<u32>) -> &'static str { if f.is_some() { "yay" } else { "nay" }}PathBuf that can cause overwrites.Callingpush with a root path at the start can overwrite theprevious defined path.
use std::path::PathBuf;let mut x = PathBuf::from("/foo");x.push("/bar");assert_eq!(x, PathBuf::from("/bar"));Could be written:
use std::path::PathBuf;let mut x = PathBuf::from("/foo");x.push("bar");assert_eq!(x, PathBuf::from("/foo/bar"));Looks for calls toPath::ends_with calls where the argument looks like a file extension.
By default, Clippy has a short list of known filenames that start with a dotbut aren’t necessarily file extensions (e.g. the.git folder), which are allowed by default.Theallowed-dotfiles configuration can be used to allow additionalfile extensions that Clippy should not lint.
This doesn’t actually compare file extensions. Rather,ends_with compares the given argumentto the lastcomponent of the path and checks if it matches exactly.
File extensions are often at most three characters long, so this only lints in those casesin an attempt to avoid false positives.Any extension names longer than that are assumed to likely be real path components and aretherefore ignored.
fn is_markdown(path: &Path) -> bool { path.ends_with(".md")}Use instead:
fn is_markdown(path: &Path) -> bool { path.extension().is_some_and(|ext| ext == "md")}allowed-dotfiles: Additional dotfiles (files or directories starting with a dot) to allow
(default:[])
Checks for calls topush immediately after creating a newPathBuf.
Multiple.join() calls are usually easier to read than multiple.pushcalls across multiple statements. It might also be possible to usePathBuf::from instead.
.join() introduces an implicitclone().PathBuf::from can alternatively beused when thePathBuf is newly constructed. This will avoid the implicit clone.
let mut path_buf = PathBuf::new();path_buf.push("foo");Use instead:
let path_buf = PathBuf::from("foo");// orlet path_buf = PathBuf::new().join("foo");Checks for patterns that aren’t exact representations of the typesthey are applied to.
To satisfy this lint, you will have to adjust either the expression that is matchedagainst or the pattern itself, as well as the bindings that are introduced by theadjusted patterns. For matching you will have to either dereference the expressionwith the* operator, or amend the patterns to explicitly match against&<pattern>or&mut <pattern> depending on the reference mutability. For the bindings you needto use the inverse. You can leave them as plain bindings if you wish for the valueto be copied, but you must useref mut <variable> orref <variable> to constructa reference into the matched structure.
If you are looking for a way to learn about ownership semantics in more detail, itis recommended to look at IDE options available to you to highlight types, lifetimesand reference semantics in your code. The available tooling would expose these thingsin a general way even outside of the various pattern matching mechanics. Of coursethis lint can still be used to highlight areas of interest and ensure a good understandingof ownership semantics.
It increases ownership hints in the code, and will guard against some changesin ownership.
This example shows the basic adjustments necessary to satisfy the lint. Note howthe matched expression is explicitly dereferenced with* and theinner variableis bound to a shared borrow viaref inner.
// Badlet value = &Some(Box::new(23));match value { Some(inner) => println!("{}", inner), None => println!("none"),}// Goodlet value = &Some(Box::new(23));match *value { Some(ref inner) => println!("{}", inner), None => println!("none"),}The following example demonstrates one of the advantages of the more verbose style.Note how the second version usesref mut a to explicitly declarea a shared mutableborrow, whileb is simply taken by value. This ensures that the loop body cannotaccidentally modify the wrong part of the structure.
// Badlet mut values = vec![(2, 3), (3, 4)];for (a, b) in &mut values { *a += *b;}// Goodlet mut values = vec![(2, 3), (3, 4)];for &mut (ref mut a, b) in &mut values { *a += b;}Checks for calls tostd::fs::Permissions.set_readonly with argumentfalse.
On Unix platforms this results in the file being world writable,equivalent tochmod a+w <file>.
use std::fs::File;let f = File::create("foo.txt").unwrap();let metadata = f.metadata().unwrap();let mut permissions = metadata.permissions();permissions.set_readonly(false);Detectspointer format as well asDebug formatting of raw pointers or function pointersor any types that have a derivedDebug impl that recursively contains them.
The addresses are only useful in very specific contexts, and certain projects may want to keep addresses ofcertain data structures or functions from prying hacker eyes as an additional line of security.
The lint currently only looks through derivedDebug implementations. Checking whether a manualimplementation prints an address is left as an exercise to the next lint implementer.
let foo = &0_u32;fn bar() {}println!("{:p}", foo);let _ = format!("{:?}", &(bar as fn()));Checks if any pointer is being passed to an asm! block withnomem option.
nomem forbids any reads or writes to memory and passing a pointer suggeststhat either of those will happen.
fn f(p: *mut u32) { unsafe { core::arch::asm!("mov [{p}], 42", p = in(reg) p, options(nomem, nostack)); }}Use instead:
fn f(p: *mut u32) { unsafe { core::arch::asm!("mov [{p}], 42", p = in(reg) p, options(nostack)); }}Checks for possible missing comma in an array. It lints ifan array element is a binary operator expression and it lies on two lines.
This could lead to unexpected results.
let a = &[ -1, -2, -3 // <= no comma here -4, -5, -6];Checks for anif expression followed by either a block or anotherif thatlooks like it should have anelse between them.
This is probably some refactoring remnant, even if the code is correct, itmight look confusing.
if foo {} { // looks like an `else` is missing here}if foo {} if bar { // looks like an `else` is missing here}Checks for operations where precedence may be unclear and suggests to add parentheses.It catches a mixed usage of arithmetic and bit shifting/combining operators,as well as method calls applied to closures.
Not everyone knows the precedence of those operators byheart, so expressions like these may trip others trying to reason about thecode.
1 << 2 + 3 equals 32, while(1 << 2) + 3 equals 7
Checks for bit shifting operations combined with bit masking/combining operatorsand suggest using parentheses.
Not everyone knows the precedence of those operators byheart, so expressions like these may trip others trying to reason about thecode.
0x2345 & 0xF000 >> 12 equals 5, while(0x2345 & 0xF000) >> 12 equals 2
Checks for usage ofprintln,print,eprintln oreprint in animplementation of a formatting trait.
Using a print macro is likely unintentional since formatting traitsshould write to theFormatter, not stdout/stderr.
use std::fmt::{Display, Error, Formatter};struct S;impl Display for S { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { println!("S"); Ok(()) }}Use instead:
use std::fmt::{Display, Error, Formatter};struct S;impl Display for S { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { writeln!(f, "S"); Ok(()) }}This lint warns about the use of literals asprint!/println! args.
Using literals asprintln! args is inefficient(c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary(i.e., just put the literal in the format string)
println!("{}", "foo");use the literal without formatting:
println!("foo");Checks for printing onstderr. The purpose of this lintis to catch debugging remnants.
People often print onstderr while debugging anapplication and might forget to remove those prints afterward.
Only catcheseprint! andeprintln! calls.
eprintln!("Hello world!");allow-print-in-tests: Whether print macros (ex.println!) should be allowed in test functions or#[cfg(test)]
(default:false)
Checks for printing onstdout. The purpose of this lintis to catch debugging remnants.
People often print onstdout while debugging anapplication and might forget to remove those prints afterward.
Only catchesprint! andprintln! calls.
println!("Hello world!");allow-print-in-tests: Whether print macros (ex.println!) should be allowed in test functions or#[cfg(test)]
(default:false)
This lint warns when you useprint!() with a formatstring that ends in a newline.
You should useprintln!() instead, which appends thenewline.
print!("Hello {}!\n", name);use println!() instead
println!("Hello {}!", name);This lint warns when you useprintln!("") toprint a newline.
You should useprintln!(), which is simpler.
println!("");Use instead:
println!();This lint checks for function arguments of type&String,&Vec,&PathBuf, andCow<_>. It will also suggest you replace.clone() callswith the appropriate.to_owned()/to_string() calls.
Requiring the argument to be of the specific typemakes 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.
There may befn(&Vec)-typed references pointing to your function.If you have them, you will get a compiler error after applying this lint’ssuggestions. You then have the choice to undo your changes or change thetype of the reference.
Note that if the function is part of your public interface, there may beother crates referencing it, of which you may not be aware. Carefullydeprecate the function before applying the lint suggestions in this case.
fn foo(&Vec<u32>) { .. }Use instead:
fn foo(&[u32]) { .. }Checks foras casts between raw pointers that don’t change theirconstness, namely*const T to*const U and*mut T to*mut U.
Thoughas casts between raw pointers are not terrible,pointer::cast is safer because it cannot accidentally change thepointer’s mutability, nor cast the pointer to other types likeusize.
let ptr: *const u32 = &42_u32;let mut_ptr: *mut u32 = &mut 42_u32;let _ = ptr as *const i32;let _ = mut_ptr as *mut i32;Use instead:
let ptr: *const u32 = &42_u32;let mut_ptr: *mut u32 = &mut 42_u32;let _ = ptr.cast::<i32>();let _ = mut_ptr.cast::<i32>();msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks foras casts between raw pointers that change their constness, namely*const T to*mut T and*mut T to*const T.
Thoughas casts between raw pointers are not terrible,pointer::cast_mut andpointer::cast_const are safer because they cannot accidentally cast the pointer to anothertype. Or, when null pointers are involved,null() andnull_mut() can be used directly.
let ptr: *const u32 = &42_u32;let mut_ptr = ptr as *mut u32;let ptr = mut_ptr as *const u32;let ptr1 = std::ptr::null::<u32>() as *mut u32;let ptr2 = std::ptr::null_mut::<u32>() as *const u32;let ptr3 = std::ptr::null::<u32>().cast_mut();let ptr4 = std::ptr::null_mut::<u32>().cast_const();Use instead:
let ptr: *const u32 = &42_u32;let mut_ptr = ptr.cast_mut();let ptr = mut_ptr.cast_const();let ptr1 = std::ptr::null_mut::<u32>();let ptr2 = std::ptr::null::<u32>();let ptr3 = std::ptr::null_mut::<u32>();let ptr4 = std::ptr::null::<u32>();Usestd::ptr::eq when applicable
ptr::eq can be used to compare&T references(which coerce to*const T implicitly) by their address rather thancomparing the values they point to.
let a = &[1, 2, 3];let b = &[1, 2, 3];assert!(a as *const _ as usize == b as *const _ as usize);Use instead:
let a = &[1, 2, 3];let b = &[1, 2, 3];assert!(std::ptr::eq(a, b));Checks for usage of theoffset pointer method with ausize casted to anisize.
If we’re always increasing the pointer address, we can avoid the numericcast by using theadd method instead.
let vec = vec![b'a', b'b', b'c'];let ptr = vec.as_ptr();let offset = 1_usize;unsafe { ptr.offset(offset as isize);}Could be written:
let vec = vec![b'a', b'b', b'c'];let ptr = vec.as_ptr();let offset = 1_usize;unsafe { ptr.add(offset);}Nothing. This lint has been deprecated
clippy::enum_variant_names now covers this case via theavoid-breaking-exported-api config.
Checks whether any field of the struct is prefixed with an_ (underscore) and also markedpub (public)
Fields prefixed with an_ are inferred as unused, which suggests it should not be markedaspub, because marking it aspub infers it will be used.
struct FileHandle { pub _descriptor: usize,}Use instead:
struct FileHandle { _descriptor: usize,}OR
struct FileHandle { pub descriptor: usize,}pub-underscore-fields-behavior: Lint “public” fields in a struct that are prefixed with an underscore based on theirexported visibility, or whether they are marked as “pub”.
(default:"PubliclyExported")
Restricts the usage ofpub use ...
A project may wish to limitpub use instances to preventunintentional exports, or to encourage placing exported items directly in public modules.
pub mod outer { mod inner { pub struct Test {} } pub use inner::Test;}use outer::Test;Use instead:
pub mod outer { pub struct Test {}}use outer::Test;Checks for usage ofpub(<loc>) within.
Consistency. Use it or don’t, just be consistent about it.
Also see thepub_without_shorthand lint for an alternative.
pub(super) type OptBox<T> = Option<Box<T>>;Use instead:
pub(in super) type OptBox<T> = Option<Box<T>>;Checks for usage ofpub(<loc>) withoutin.
Note: As you cannot write a module’s path inpub(<loc>), this will only trigger onpub(super) and the like.
Consistency. Use it or don’t, just be consistent about it.
Also see thepub_with_shorthand lint for an alternative.
pub(in super) type OptBox<T> = Option<Box<T>>;Use instead:
pub(super) type OptBox<T> = Option<Box<T>>;Checks for expressions that could be replaced by the? operator.
Using the? operator is shorter and more idiomatic.
if option.is_none() { return None;}Could be written:
option?;msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for expressions that use the? operator and rejects them.
Sometimes code wants to avoid the? operator because for instance a localblock requires a macro to re-throw errors to attach additional information to theerror.
let result = expr?;Could be written:
utility_macro!(expr);Checks for inclusive ranges where 1 is subtracted fromthe upper bound, e.g.,x..=(y-1).
The code is more readable with an exclusive rangelikex..y.
The lint is conservative and will trigger only when switchingfrom an inclusive to an exclusive range is provably safe froma typing point of view. This corresponds to situations wherethe range is used as an iterator, or for indexing.
for i in x..=(y-1) { // ..}Use instead:
for i in x..y { // ..}Checks for exclusive ranges where 1 is added to theupper bound, e.g.,x..(y+1).
The code is more readable with an inclusive rangelikex..=y.
The lint is conservative and will trigger only when switchingfrom an exclusive to an inclusive range is provably safe froma typing point of view. This corresponds to situations wherethe range is used as an iterator, or for indexing.
Will add unnecessary pair of parentheses when theexpression is not wrapped in a pair but starts with an opening parenthesisand ends with a closing one.I.e.,let _ = (f()+1)..(f()+1) results inlet _ = ((f()+1)..=f()).
Also in many cases, inclusive ranges are still slower to run thanexclusive ranges, because they essentially add an extra branch thatLLVM may fail to hoist out of the loop.
for i in x..(y+1) { // ..}Use instead:
for i in x..=y { // ..}Nothing. This lint has been deprecated
Iterator::step_by(0) now panics and is no longer an infinite iterator.
Checks for zipping a collection with the range of0.._.len().
The code is better expressed with.enumerate().
let _ = x.iter().zip(0..x.len());Use instead:
let _ = x.iter().enumerate();Checks forRc<T> andArc<T> whenT is a mutable buffer type such asString orVec.
Expressions such asRc<String> usually have no advantage overRc<str>, sinceit is larger and involves an extra level of indirection, and doesn’t implementBorrow<str>.
While mutating a buffer type would still be possible withRc::get_mut(), it onlyworks if there are no additional references yet, which usually defeats the purpose ofenclosing it in a shared ownership type. Instead, additionally wrapping the innertype with an interior mutable container (such asRefCell orMutex) would normallybe used.
This pattern can be desirable to avoid the overhead of aRefCell orMutex forcases where mutation only happens before there are any additional references.
fn foo(interned: Rc<String>) { ... }Better:
fn foo(interned: Rc<str>) { ... }avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Checks for reference-counted pointers (Arc,Rc,rc::Weak, andsync::Weak)invec![elem; len]
This will createelem once and clone itlen times - doing so withArc/Rc/Weakis a bit misleading, as it will create references to the same pointer, ratherthan different instances.
let v = vec![std::sync::Arc::new("some data".to_string()); 100];// orlet v = vec![std::rc::Rc::new("some data".to_string()); 100];Use instead:
// Initialize each value separately:let mut data = Vec::with_capacity(100);for _ in 0..100 { data.push(std::rc::Rc::new("some data".to_string()));}// Or if you want clones of the same reference,// Create the reference beforehand to clarify that// it should be cloned for each valuelet data = std::rc::Rc::new("some data".to_string());let v = vec![data; 100];Checks forRc<Mutex<T>>.
Rc is used in single thread andMutex is used in multi thread.Consider usingRc<RefCell<T>> in single thread orArc<Mutex<T>> in multi thread.
Sometimes combining generic types can lead to the requirement that atype use Rc in conjunction with Mutex. We must consider those cases false positives, butalas they are quite hard to rule out. Luckily they are also rare.
use std::rc::Rc;use std::sync::Mutex;fn foo(interned: Rc<Mutex<i32>>) { ... }Better:
use std::rc::Rc;use std::cell::RefCellfn foo(interned: Rc<RefCell<i32>>) { ... }avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Looks for calls to [Stdin::read_line] to read a line from the standard inputinto a string, then later attempting to use that string for an operation that will neverwork for strings with a trailing newline character in it (e.g. parsing into ai32).
The operation will always fail at runtime no matter what the user enters, thusmaking it a useless operation.
let mut input = String::new();std::io::stdin().read_line(&mut input).expect("Failed to read a line");let num: i32 = input.parse().expect("Not a number!");assert_eq!(num, 42); // we never even get here!Use instead:
let mut input = String::new();std::io::stdin().read_line(&mut input).expect("Failed to read a line");let num: i32 = input.trim_end().parse().expect("Not a number!");// ^^^^^^^^^^^ remove the trailing newlineassert_eq!(num, 42);This lint catches reads into a zero-lengthVec.Especially in the case of a call towith_capacity, this lint warns that readgets the number of bytes from theVec’s length, not its capacity.
Reading zero bytes is almost certainly not the intended behavior.
In theory, a very unusual read implementation could assign some semantic meaningto zero-byte reads. But it seems exceptionally unlikely that code intending to doa zero-byte read would allocate aVec for it.
use std::io;fn foo<F: io::Read>(mut f: F) { let mut data = Vec::with_capacity(100); f.read(&mut data).unwrap();}Use instead:
use std::io;fn foo<F: io::Read>(mut f: F) { let mut data = Vec::with_capacity(100); data.resize(100, 0); f.read(&mut data).unwrap();}Looks for calls toRwLock::write where the lock is only used for reading.
The write portion ofRwLock is exclusive, meaning that no other threadcan access the lock while this writer is active.
use std::sync::RwLock;fn assert_is_zero(lock: &RwLock<i32>) { let num = lock.write().unwrap(); assert_eq!(*num, 0);}Use instead:
use std::sync::RwLock;fn assert_is_zero(lock: &RwLock<i32>) { let num = lock.read().unwrap(); assert_eq!(*num, 0);}Checks for format trait implementations (e.g.Display) with a recursive call to itselfwhich usesself as a parameter.This is typically done indirectly with thewrite! macro or withto_string().
This will lead to infinite recursion and a stack overflow.
use std::fmt;struct Structure(i32);impl fmt::Display for Structure { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.to_string()) }}Use instead:
use std::fmt;struct Structure(i32);impl fmt::Display for Structure { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }}Checks for usage of redundant allocations anywhere in the code.
Expressions such asRc<&T>,Rc<Rc<T>>,Rc<Arc<T>>,Rc<Box<T>>,Arc<&T>,Arc<Rc<T>>,Arc<Arc<T>>,Arc<Box<T>>,Box<&T>,Box<Rc<T>>,Box<Arc<T>>,Box<Box<T>>, add an unnecessary level of indirection.
fn foo(bar: Rc<&usize>) {}Better:
fn foo(bar: &usize) {}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Checks for usage ofas_str() on aString chained with a method available on theString itself.
Theas_str() conversion is pointless and can be removed for simplicity and cleanliness.
let owned_string = "This is a string".to_owned();owned_string.as_str().as_bytes()Use instead:
let owned_string = "This is a string".to_owned();owned_string.as_bytes()Checks forasync block that only returnsawait on a future.
It is simpler and more efficient to use the future directly.
let f = async { 1 + 2};let fut = async { f.await};Use instead:
let f = async { 1 + 2};let fut = f;Checks for[all @ ..] patterns.
In all cases,all works fine and can often make code simpler, as you possibly won’t needto convert from say aVec to a slice by dereferencing.
if let [all @ ..] = &*v { // NOTE: Type is a slice here println!("all elements: {all:#?}");}Use instead:
if let all = v { // NOTE: Type is a `Vec` here println!("all elements: {all:#?}");}// orprintln!("all elements: {v:#?}");Checks for a redundantclone() (and its relatives) which clones an ownedvalue that is going to be dropped without further use.
It is not always possible for the compiler to eliminate uselessallocations and deallocations generated by redundantclone()s.
False-negatives: analysis performed by this lint is conservative and limited.
{ let x = Foo::new(); call(x.clone()); call(x.clone()); // this can just pass `x`}["lorem", "ipsum"].join(" ").to_string();Path::new("/a/b").join("c").to_path_buf();Checks for closures which just call another function wherethe function can be called directly.unsafe functions, calls where typesget adjusted or where the callee is marked#[track_caller] are ignored.
Needlessly creating a closure adds code for no benefitand gives the optimizer more work.
xs.map(|x| foo(x))Use instead:
// where `foo(_)` is a plain function that takes the exact argument type of `x`.xs.map(foo)Detects closures called in the same expression where theyare defined.
It is unnecessarily adding to the expression’scomplexity.
let a = (|| 42)();Use instead:
let a = 42;Checks for closures which only invoke a method on the closureargument and can be replaced by referencing the method directly.
It’s unnecessary to create the closure.
Some('a').map(|s| s.to_uppercase());may be rewritten as
Some('a').map(char::to_uppercase);Checks for ineffective double comparisons against constants.
Only one of the comparisons has any effect on the result, the programmerprobably intended to flip one of the comparison operators, or compare adifferent value entirely.
if status_code <= 400 && status_code < 500 {}Checks forelse blocks that can be removed without changing semantics.
Theelse block adds unnecessary indentation and verbosity.
Some may prefer to keep theelse block for clarity.
fn my_func(count: u32) { if count == 0 { print!("Nothing to do"); return; } else { print!("Moving on..."); }}Use instead:
fn my_func(count: u32) { if count == 0 { print!("Nothing to do"); return; } print!("Moving on...");}Checks for feature names with prefixuse-,with- or suffix-support
These prefixes and suffixes have no significant meaning.
[features]default = ["use-abc", "with-def", "ghi-support"]use-abc = [] // redundantwith-def = [] // redundantghi-support = [] // redundantUse instead:
[features]default = ["abc", "def", "ghi"]abc = []def = []ghi = []Checks for fields in struct literals where shorthandscould be used.
If the field and variable names are the same,the field name is redundant.
let bar: u8 = 123;struct Foo { bar: u8,}let foo = Foo { bar: bar };the last line can be simplified to
let foo = Foo { bar };msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for unnecessary guards in match expressions.
It’s more complex and much less readable. Making it part of the pattern can improveexhaustiveness checking as well.
match x { Some(x) if matches!(x, Some(1)) => .., Some(x) if x == Some(2) => .., _ => todo!(),}Use instead:
match x { Some(Some(1)) => .., Some(Some(2)) => .., _ => todo!(),}Checks for calls toIterator::cloned where the original value could be usedinstead.
It is not always possible for the compiler to eliminate useless allocations anddeallocations generated by redundantclone()s.
let x = vec![String::new()];let _ = x.iter().cloned().map(|x| x.len());Use instead:
let x = vec![String::new()];let _ = x.iter().map(|x| x.len());Checks for redundant redefinitions of local bindings.
Redundant redefinitions of local bindings do not change behavior other than variable’s lifetimes and are likely to be unintended.
These rebindings can be intentional to shorten the lifetimes of variables because they affect when theDrop implementation is called. Other than that, they do not affect your code’s meaning but theymay affectrustc’s stack allocation.
let a = 0;let a = a;fn foo(b: i32) { let b = b;}Use instead:
let a = 0;// no redefinition with the same namefn foo(b: i32) { // no redefinition with the same name}Checks for patterns in the formname @ _.
It’s almost always more readable to just use directbindings.
match v { Some(x) => (), y @ _ => (),}Use instead:
match v { Some(x) => (), y => (),}Lint for redundant pattern matching overResult,Option,std::task::Poll,std::net::IpAddr orbools
It’s more concise and clear to just use the properutility function or using the condition directly
For suggestions involving bindings in patterns, this will change the drop order for the matched type.Bothif let andwhile let will drop the value at the end of the block, bothif andwhile will drop thevalue before entering the block. For most types this change will not matter, but for a fewtypes this will not be an acceptable change (e.g. locks). See thereference for more aboutdrop order.
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);The more idiomatic use would be:
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;Checks for items declaredpub(crate) that are not crate visible because theyare inside a private module.
Writingpub(crate) is misleading when it’s redundant due to the parentmodule’s visibility.
mod internal { pub(crate) fn internal_fn() { }}This function is not visible outside the module and it can be declared withpub orprivate visibility
mod internal { pub fn internal_fn() { }}Checks for redundant slicing expressions which use the full range, anddo not change the type.
It unnecessarily adds complexity to the expression.
If the type being sliced has an implementation ofIndex<RangeFull>that actually changes anything then it can’t be removed. However, this would be surprisingto people reading the code and should have a note with it.
fn get_slice(x: &[u32]) -> &[u32] { &x[..]}Use instead:
fn get_slice(x: &[u32]) -> &[u32] { x}Checks for constants and statics with an explicit'static lifetime.
Adding'static to every reference can create verycomplicated types.
const FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] =&[...]static FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] =&[...]This code can be rewritten as
const FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...] static FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...]msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for test functions (functions annotated with#[test]) that are prefixedwithtest_ which is redundant.
This is redundant because test functions are already annotated with#[test].Moreover, it clutters the output ofcargo test since test functions are expanded asmodule::tests::test_use_case in the output. Without the redundant prefix, the outputbecomesmodule::tests::use_case, which is more readable.
#[cfg(test)]mod tests { use super::*; #[test] fn test_use_case() { // test code }}Use instead:
#[cfg(test)]mod tests { use super::*; #[test] fn use_case() { // test code }}Warns about needless / redundant type annotations.
Code without type annotations is shorter and in most casesmore idiomatic and easier to modify.
This lint doesn’t support:
MethodCallPath to anything else than a primitive type.let foo: String = String::new();Use instead:
let foo = String::new();Checks for casts of references to pointer usingasand suggestsstd::ptr::from_ref andstd::ptr::from_mut instead.
Usingas casts may result in silently changing mutability or type.
let a_ref = &1;let a_ptr = a_ref as *const _;Use instead:
let a_ref = &1;let a_ptr = std::ptr::from_ref(a_ref);Checks forref bindings which create a reference to a reference.
The address-of operator at the use site is clearer about the need for a reference.
let x = Some("");if let Some(ref x) = x { // use `x` here}Use instead:
let x = Some("");if let Some(x) = x { // use `&x` here}Warns when a function signature uses&Option<T> instead ofOption<&T>.
More flexibility, better memory optimization, and more idiomatic Rust code.
&Option<T> in a function signature breaks encapsulation because the caller must own Tand move it into an Option to call with it. When returned, the owner must internally storeit asOption<T> in order to return it.At a lower level,&Option<T> points to memory with thepresence bit flag plus theT value,whereasOption<&T> is usuallyoptimizedto a single pointer, so it may be more optimal.
See thisYouTube video byLogan Smith for an in-depth explanation of why this is important.
This lint recommends changing the function signatures, but it cannotautomatically change the function calls or the function implementations.
// caller uses foo(&opt)fn foo(a: &Option<String>) {}fn bar(&self) -> &Option<String> { &None }Use instead:
// caller should use `foo1(opt.as_ref())`fn foo1(a: Option<&String>) {}// better yet, use string slice `foo2(opt.as_deref())`fn foo2(a: Option<&str>) {}fn bar(&self) -> Option<&String> { None }avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Checks for usage of&Option<&T>.
Since& is Copy, it’s useless to have areference onOption<&T>.
It may be irrelevant to use this lint onpublic API code as it will make a breaking change to apply it.
let x: &Option<&u32> = &Some(&0u32);Use instead:
let x: Option<&u32> = Some(&0u32);Checks for usages of theref keyword.
Theref keyword can be confusing for people unfamiliar with it, and oftenit is more concise to use& instead.
let opt = Some(5);if let Some(ref foo) = opt {}Use instead:
let opt = Some(5);if let Some(foo) = &opt {}Checks forregex compilation inside a loop with a literal.
Compiling a regex is a much more expensive operation than using one, and a compiled regex can be used multiple times.This is documented as an antipatternon the regex documentation
for haystack in haystacks { let regex = regex::Regex::new(MY_REGEX).unwrap(); if regex.is_match(haystack) { // Perform operation }}can be replaced with
let regex = regex::Regex::new(MY_REGEX).unwrap();for haystack in haystacks { if regex.is_match(haystack) { // Perform operation }}Nothing. This lint has been deprecated
Theregex! macro was removed from the regex crate in 2018.
Lints when the name of function parameters from trait impl isdifferent than its default implementation.
Using the default name for parameters of a trait method is more consistent.
struct A(u32);impl PartialEq for A { fn eq(&self, b: &Self) -> bool { self.0 == b.0 }}Use instead:
struct A(u32);impl PartialEq for A { fn eq(&self, other: &Self) -> bool { self.0 == other.0 }}allow-renamed-params-for: List of trait paths to ignore when checking renamed function parameters.allow-renamed-params-for = [ "std::convert::From" ]By default, the following traits are ignored:From,TryFrom,FromStr
".." can be used as part of the list to indicate that the configured values should be appended to thedefault configuration of Clippy. By default, any configuration will replace the default value.
(default:["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"])
Checks for usage of.repeat(1) and suggest the following method for each types.
.to_string() forstr.clone() forString.to_vec() forsliceThe lint will evaluate constant expressions and values as arguments of.repeat(..) and emit a message ifthey are equivalent to1. (Related discussion inrust-clippy#7306)
For example,String.repeat(1) is equivalent to.clone(). If cloningthe string is the intention behind this,clone() should be used.
fn main() { let x = String::from("hello world").repeat(1);}Use instead:
fn main() { let x = String::from("hello world").clone();}Looks for patterns such asvec![Vec::with_capacity(x); n] oriter::repeat(Vec::with_capacity(x)).
These constructs work by cloning the element, but cloning aVec<_> does notrespect the old vector’s capacity and effectively discards it.
This makesiter::repeat(Vec::with_capacity(x)) especially suspicious because the user most certainlyexpected that the yieldedVec<_> will have the requested capacity, otherwise one can simply writeiter::repeat(Vec::new()) instead and it will have the same effect.
Similarly forvec![x; n], the elementx is cloned to fill the vec.Unlikeiter::repeat however, the vec repeat macro does not have to clone the valuen timesbut justn - 1 times, because it can reuse the passed value for the last slot.That means that the lastVec<_> gets the requested capacity but all other ones do not.
let _: Vec<Vec<u8>> = vec![Vec::with_capacity(42); 123];let _: Vec<Vec<u8>> = iter::repeat(Vec::with_capacity(42)).take(123).collect();Use instead:
let _: Vec<Vec<u8>> = iter::repeat_with(|| Vec::with_capacity(42)).take(123).collect();// ^^^ this closure executes 123 times// and the vecs will have the expected capacitymsrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Detects assignments ofDefault::default() orBox::new(value)to a place of typeBox<T>.
This incurs an extra heap allocation compared to assigning the boxedstorage.
let mut b = Box::new(1u32);b = Default::default();Use instead:
let mut b = Box::new(1u32);*b = Default::default();Nothing. This lint has been deprecated
min_value andmax_value are now deprecated.
Checks for items with#[repr(packed)]-attribute without ABI qualification
Without qualification,repr(packed) impliesrepr(Rust). The Rust-ABI is inherently unstable.While this is fine as long as the type is accessed correctly within Rust-code, most usesof#[repr(packed)] involve FFI and/or data structures specified by network-protocols orother external specifications. In such situations, the unstable Rust-ABI implied in#[repr(packed)] may lead to future bugs should the Rust-ABI change.
In case you are relying on a well defined and stable memory layout, qualify the type’srepresentation using theC-ABI. Otherwise, if the type in question is only everaccessed from Rust-code according to Rust’s rules, use theRust-ABI explicitly.
#[repr(packed)]struct NetworkPacketHeader { header_length: u8, header_version: u16}Use instead:
#[repr(C, packed)]struct NetworkPacketHeader { header_length: u8, header_version: u16}Informs the user about a more concise way to create a vector with a known capacity.
TheVec::with_capacity constructor is less complex.
let mut v: Vec<usize> = vec![];v.reserve(10);Use instead:
let mut v: Vec<usize> = Vec::with_capacity(10);Checks for unnecessary ‘..’ pattern binding on struct when all fields are explicitly matched.
Correctness and readability. It’s like having a wildcard pattern aftermatching all enum variants explicitly.
let a = A { a: 5 };match a { A { a: 5, .. } => {}, _ => {},}Use instead:
match a { A { a: 5 } => {}, _ => {},}Checks for iterators ofResults using.filter(Result::is_ok).map(Result::unwrap) that maybe replaced with a.flatten() call.
Result implementsIntoIterator<Item = T>. This means thatResult can be flattenedautomatically without suspicious-lookingunwrap calls.
let _ = std::iter::empty::<Result<i32, ()>>().filter(Result::is_ok).map(Result::unwrap);Use instead:
let _ = std::iter::empty::<Result<i32, ()>>().flatten();Checks for functions that returnResult with an unusually largeErr-variant.
AResult is at least as large as theErr-variant. While weexpect that variant to be seldom used, the compiler needs to reserveand move that much memory every single time.Furthermore, errors are often simply passed up the call-stack, makinguse of the?-operator and its type-conversion mechanics. If theErr-variant further up the call-stack stores theErr-variant inquestion (as library code often does), it itself needs to be at leastas large, propagating the problem.
The size determined by Clippy is platform-dependent.
pub enum ParseError { UnparsedBytes([u8; 512]), UnexpectedEof,}// The `Result` has at least 512 bytes, even in the `Ok`-casepub fn parse() -> Result<(), ParseError> { Ok(())}should be
pub enum ParseError { UnparsedBytes(Box<[u8; 512]>), UnexpectedEof,}// The `Result` is slightly larger than a pointerpub fn parse() -> Result<(), ParseError> { Ok(())}large-error-threshold: The maximum size of theErr-variant in aResult returned from a function
(default:128)
Checks for usage of_.map_or(None, Some).
Readability, this can be written more concisely as_.ok().
assert_eq!(Some(1), r.map_or(None, Some));Use instead:
assert_eq!(Some(1), r.ok());Checks for usage ofresult.map(f) where f is a functionor closure that returns the unit type().
Readability, this can be written more clearly withan if let statement
let x: Result<String, String> = do_stuff();x.map(log_err_msg);x.map(|msg| log_err_msg(format_msg(msg)));The correct use would be:
let x: Result<String, String> = do_stuff();if let Ok(msg) = x { log_err_msg(msg);};if let Ok(msg) = x { log_err_msg(format_msg(msg));};Checks for public functions that return aResultwith anErr type of(). It suggests using a custom type thatimplementsstd::error::Error.
Unit does not implementError and carries nofurther information about what went wrong.
Of course, this lint assumes thatResult is usedfor a fallible operation (which is after all the intended use). Howevercode may opt to (mis)use it as a basic two-variant-enum. In that case,the suggestion is misguided, and the code should use a custom enuminstead.
pub fn read_u8() -> Result<u8, ()> { Err(()) }should become
use std::fmt;#[derive(Debug)]pub struct EndOfStream;impl fmt::Display for EndOfStream { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "End of Stream") }}impl std::error::Error for EndOfStream { }pub fn read_u8() -> Result<u8, EndOfStream> { Err(EndOfStream) }Note that there are crates that simplify creating the error type, e.g.thiserror.
Detect functions that end withOption::and_then orResult::and_then, and suggest usingthe? operator instead.
Theand_then method is used to chain a computation that returns anOption or aResult.This can be replaced with the? operator, which is more concise and idiomatic.
fn test(opt: Option<i32>) -> Option<i32> { opt.and_then(|n| { if n > 1 { Some(n + 1) } else { None } })}Use instead:
fn test(opt: Option<i32>) -> Option<i32> { let n = opt?; if n > 1 { Some(n + 1) } else { None }}This lint warns when a method returningSelf doesn’t have the#[must_use] attribute.
Methods returningSelf often create new values, having the#[must_use] attributeprevents users from “forgetting” to use the newly created value.
The#[must_use] attribute can be added to the type itself to ensure that instancesare never forgotten. Functions returning a type marked with#[must_use] will not belinted, as the usage is already enforced by the type attribute.
This lint is only applied on methods taking aself argument. It would be mostly noiseif it was added on constructors for example.
pub struct Bar;impl Bar { // Missing attribute pub fn bar(&self) -> Self { Self }}Use instead:
// It's better to have the `#[must_use]` attribute on the method like this:pub struct Bar;impl Bar { #[must_use] pub fn bar(&self) -> Self { Self }}// Or on the type definition like this:#[must_use]pub struct Bar;impl Bar { pub fn bar(&self) -> Self { Self }}Checks for range expressionsx..y where bothx andyare constant andx is greater toy. Also triggers ifx is equal toy when they are conditions to afor loop.
Empty ranges yield no values so iterating them is a no-op.Moreover, trying to use a reversed range to index a slice will panic at run-time.
fn main() { (10..=0).for_each(|x| println!("{}", x)); let arr = [1, 2, 3, 4, 5]; let sub = &arr[3..1];}Use instead:
fn main() { (0..=10).rev().for_each(|x| println!("{}", x)); let arr = [1, 2, 3, 4, 5]; let sub = &arr[1..3];}Checks for consecutiveifs with the same function call.
This is probably a copy & paste error.Despite the fact that function can have side effects andif works asintended, such an approach is implicit and can be considered a “code smell”.
if foo() == bar { …} else if foo() == bar { …}This probably should be:
if foo() == bar { …} else if foo() == baz { …}or if the original code was not a typo and called function mutates a state,consider move the mutation out of theif condition to avoid similarity toa copy & paste error:
let first = foo();if first == bar { …} else { let second = foo(); if second == bar { … }}Checks whether a for loop is being used to push a constantvalue into a Vec.
This kind of operation can be expressed more succinctly withvec![item; SIZE] orvec.resize(NEW_SIZE, item) and using these alternatives may alsohave better performance.
let item1 = 2;let item2 = 3;let mut vec: Vec<u8> = Vec::new();for _ in 0..20 { vec.push(item1);}for _ in 0..30 { vec.push(item2);}Use instead:
let item1 = 2;let item2 = 3;let mut vec: Vec<u8> = vec![item1; 20];vec.resize(20 + 30, item2);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
It lints if a struct has two methods with the same name:one from a trait, another not from a trait.
Confusing.
trait T { fn foo(&self) {}}struct S;impl T for S { fn foo(&self) {}}impl S { fn foo(&self) {}}Checks for an iterator or string search (such asfind(),position(), orrposition()) followed by a call tois_some() oris_none().
Readability, this can be written more concisely as:
_.any(_), or_.contains(_) foris_some(),!_.any(_), or!_.contains(_) foris_none().let vec = vec![1];vec.iter().find(|x| **x == 0).is_some();"hello world".find("world").is_none();Use instead:
let vec = vec![1];vec.iter().any(|x| *x == 0);!"hello world".contains("world");Checks if theseek method of theSeek trait is called withSeekFrom::Current(0),and if it is, suggests usingstream_position instead.
Readability. Use dedicated method.
use std::fs::File;use std::io::{self, Write, Seek, SeekFrom};fn main() -> io::Result<()> { let mut f = File::create("foo.txt")?; f.write_all(b"Hello")?; eprintln!("Written {} bytes", f.seek(SeekFrom::Current(0))?); Ok(())}Use instead:
use std::fs::File;use std::io::{self, Write, Seek, SeekFrom};fn main() -> io::Result<()> { let mut f = File::create("foo.txt")?; f.write_all(b"Hello")?; eprintln!("Written {} bytes", f.stream_position()?); Ok(())}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for jumps to the start of a stream that implementsSeekand uses theseek method providingStart as parameter.
Readability. There is a specific method that was implemented forthis exact scenario.
fn foo<T: io::Seek>(t: &mut T) { t.seek(io::SeekFrom::Start(0));}Use instead:
fn foo<T: io::Seek>(t: &mut T) { t.rewind();}Checks for explicit self-assignments.
Self-assignments are redundant and unlikely to beintentional.
If expression contains any deref coercions orindexing operations they are assumed not to have any side effects.
struct Event { x: i32,}fn copy_position(a: &mut Event, b: &Event) { a.x = a.x;}Should be:
struct Event { x: i32,}fn copy_position(a: &mut Event, b: &Event) { a.x = b.x;}Warns when constructors have the same name as their types.
Repeating the name of the type is redundant.
struct Foo {}impl Foo { pub fn foo() -> Foo { Foo {} }}Use instead:
struct Foo {}impl Foo { pub fn new() -> Foo { Foo {} }}Checks that module layout uses onlymod.rs files.
Having multiple module layout styles in a project can be confusing.
src/ stuff/ stuff_files.rs stuff.rs lib.rsUse instead:
src/ stuff/ stuff_files.rs mod.rs lib.rsChecks forself receiver that is only used in recursion with no side-effects.
It may be possible to remove theself argument, allowing the function to beused without an object of typeSelf.
struct Foo;impl Foo { fn f(&self, n: u32) -> u32 { if n == 0 { 1 } else { n * self.f(n - 1) } }}Use instead:
struct Foo;impl Foo { fn f(n: u32) -> u32 { if n == 0 { 1 } else { n * Self::f(n - 1) } }}Too many code paths in the linting code are currently untested and prone to produce falsepositives or are prone to have performance implications.
In some cases, this would not catch all useless arguments.
struct Foo;impl Foo { fn foo(&self, a: usize) -> usize { let f = |x| x; if a == 0 { 1 } else { f(self).foo(a) } }}For example, hereself is only used in recursion, but the lint would not catch it.
List of some examples that can not be caught:
break relative operationsAlso, when you recurse the function name with path segments, it is not possible to detect.
Looks for blocks of expressions and fires if the last expression returns() but is not followed by a semicolon.
The semicolon might be optional but when extending the block with newcode, it doesn’t require a change in previous last line.
fn main() { println!("Hello world")}Use instead:
fn main() { println!("Hello world");}Suggests moving the semicolon after a block to the inside of the block, after its lastexpression.
For consistency it’s best to have the semicolon inside/outside the block. Either way is fineand this lint suggests inside the block.Take a look atsemicolon_outside_block for the other alternative.
unsafe { f(x) };Use instead:
unsafe { f(x); }semicolon-inside-block-ignore-singleline: Whether to lint only if it’s multiline.
(default:false)
Suggests moving the semicolon from a block’s final expression outside of the block.
For consistency it’s best to have the semicolon inside/outside the block. Either way is fineand this lint suggests outside the block.Take a look atsemicolon_inside_block for the other alternative.
unsafe { f(x); }Use instead:
unsafe { f(x) };semicolon-outside-block-ignore-multiline: Whether to lint only if it’s singleline.
(default:false)
Warns if literal suffixes are separated by an underscore.To enforce separated literal suffix style,see theunseparated_literal_suffix lint.
Suffix style should be consistent.
123832_i32Use instead:
123832i32Checks for misuses of the serde API.
Serde is very finicky about how its API should beused, but the type system can’t be used to enforce it (yet?).
ImplementingVisitor::visit_string but notVisitor::visit_str.
Checks for usage ofcontains to see if a value is not presentin a set likeHashSet orBTreeSet, followed by aninsert.
Using justinsert and checking the returnedbool is more efficient.
In case the value that wants to be inserted is borrowed and also expensive or impossibleto clone. In such a scenario, the developer might want to check withcontains before inserting,to avoid the clone. In this case, it will report a false positive.
use std::collections::HashSet;let mut set = HashSet::new();let value = 5;if !set.contains(&value) { set.insert(value); println!("inserted {value:?}");}Use instead:
use std::collections::HashSet;let mut set = HashSet::new();let value = 5;if set.insert(&value) { println!("inserted {value:?}");}Checks for bindings that shadow other bindings already inscope, while reusing the original value.
Some argue that name shadowing like this hurts readability,because a value may be bound to different things depending on position inthe code.
See alsoshadow_same andshadow_unrelated for other restrictions on shadowing.
let x = 2;let x = x + 1;use different variable name:
let x = 2;let y = x + 1;Checks for bindings that shadow other bindings already inscope, while just changing reference level or mutability.
To require that what are formally distinct variables be given distinct names.
See alsoshadow_reuse andshadow_unrelated for other restrictions on shadowing.
let x = &x;Use instead:
let y = &x; // use different variable nameChecks for bindings that shadow other bindings already inscope, either without an initialization or with one that does not even usethe original value.
Shadowing a binding with a closely related one is part of idiomatic Rust,but shadowing a binding by accident with an unrelated one may indicate a mistake.
Additionally, name shadowing in general can hurt readability, especially inlarge code bases, because it is easy to lose track of the active binding atany place in the code. If linting against all shadowing is desired, you may wishto use theshadow_same andshadow_reuse lints as well.
let x = y;let x = z; // shadows the earlier bindingUse instead:
let x = y;let w = z; // use different variable nameChecks for the use of short circuit boolean conditions asastatement.
Using a short circuit boolean condition as a statementmay hide the fact that the second part is executed or not depending on theoutcome of the first part.
f() && g(); // We should write `if f() { g(); }`.Nothing. This lint has been deprecated
assert!(a == b) can now print the values the same way `assert_eq!(a, b) can.
Checks for methods that should live in a traitimplementation of astd trait (seellogiq’s blogpost for furtherinformation) instead of an inherent implementation.
Implementing the traits improve ergonomics for users ofthe code, often with very little cost. Also people seeing amul(...)methodmay expect* to work equally, so you should have good reason to disappointthem.
struct X;impl X { fn add(&self, other: &X) -> X { // .. }}Checks for#[should_panic] attributes without specifying the expected panic message.
The expected panic message should be specified to ensure that the test is actuallypanicking with the expected message, and not another unrelated panic.
fn random() -> i32 { 0 }#[should_panic]#[test]fn my_test() { let _ = 1 / random();}Use instead:
fn random() -> i32 { 0 }#[should_panic = "attempt to divide by zero"]#[test]fn my_test() { let _ = 1 / random();}Checks for temporaries returned from function calls in a match scrutinee that have theclippy::has_significant_drop attribute.
Theclippy::has_significant_drop attribute can be added to types whose Drop impls havean important side-effect, such as unlocking a mutex, making it important for users to beable to accurately understand their lifetimes. When a temporary is returned in a functioncall in a match scrutinee, its lifetime lasts until the end of the match block, which maybe surprising.
ForMutexes this can lead to a deadlock. This happens when the match scrutinee uses afunction call that returns aMutexGuard and then tries to lock again in one of the matcharms. In that case theMutexGuard in the scrutinee will not be dropped until the end ofthe match block and thus will not unlock.
let mutex = Mutex::new(State {});match mutex.lock().unwrap().foo() { true => { mutex.lock().unwrap().bar(); // Deadlock! } false => {}};println!("All done!");Use instead:
let mutex = Mutex::new(State {});let is_foo = mutex.lock().unwrap().foo();match is_foo { true => { mutex.lock().unwrap().bar(); } false => {}};println!("All done!");Searches for elements marked with#[clippy::has_significant_drop] that could be earlydropped but are in fact dropped at the end of their scopes. In other words, enforces the“tightening” of their possible lifetimes.
Elements marked with#[clippy::has_significant_drop] are generally synchronizingprimitives that manage shared resources, as such, it is desired to release them as soon aspossible to avoid unnecessary resource contention.
fn main() { let lock = some_sync_resource.lock(); let owned_rslt = lock.do_stuff_with_resource(); // Only `owned_rslt` is needed but `lock` is still held. do_heavy_computation_that_takes_time(owned_rslt);}Use instead:
fn main() { let owned_rslt = some_sync_resource.lock().do_stuff_with_resource(); do_heavy_computation_that_takes_time(owned_rslt);}Checks for names that are very similar and thus confusing.
Note: this lint looks for similar names throughout eachscope. To allow it, you need to allow it on the scopelevel, not on the name that is reported.
It’s hard to distinguish between names that differ onlyby a single character.
let checked_exp = something;let checked_expr = something_else;Checks for functions that are only used once. Does not lint tests.
If a function is only used once (perhaps because it used to be used more widely),then the code could be simplified by moving that function’s code into its caller.
However, there are reasons not to do this everywhere:
If this lint is used, prepare to#[allow] it a lot.
pub fn a<T>(t: &T)where T: AsRef<str>,{ a_inner(t.as_ref())}fn a_inner(t: &str) { /* snip */}Use instead:
pub fn a<T>(t: &T)where T: AsRef<str>,{ let t = t.as_ref(); /* snip */}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Warns when usingpush_str/insert_str with a single-character string literalwherepush/insert with achar would work fine.
It’s less clear that we are pushing a single character.
string.insert_str(0, "R");string.push_str("R");Use instead:
string.insert(0, 'R');string.push('R');Checks for lifetimes with names which are one characterlong.
A single character is likely not enough to express thepurpose of a lifetime. Using a longer name can make codeeasier to understand.
Rust programmers and learning resources tend to use singlecharacter lifetimes, so this lint is at odds with theecosystem at large. In addition, the lifetime’s purpose maybe obvious or, rarely, expressible in one character.
struct DiagnosticCtx<'a> { source: &'a str,}Use instead:
struct DiagnosticCtx<'src> { source: &'src str,}Checks for string methods that receive a single-characterstr as an argument, e.g.,_.split("x").
While this can make a perf difference on some systems,benchmarks have proven inconclusive. But at least using achar literal makes it clear that we are looking at a singlecharacter.
Does not catch multi-byte unicode characters. This is bydesign, on many machines, splitting by a non-ascii char isactually slower. Please do your own measurements instead ofrelying solely on the results of this lint.
_.split("x");Use instead:
_.split('x');Checking for imports with single component use path.
Import with single component use path such asuse cratename;is not necessary, and thus should be removed.
use regex;fn main() { regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();}Better as
fn main() { regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();}Checks whether a for loop has a single element.
There is no reason to have a loop of asingle element.
let item1 = 2;for item in &[item1] { println!("{}", item);}Use instead:
let item1 = 2;let item = &item1;println!("{}", item);Checks for matches with a single arm where anif letwill usually suffice.
This intentionally does not lint if there are commentsinside of the other arm, so as to allow the user to documentwhy having another explicit pattern with an empty body is necessary,or because the comments need to be preserved for other reasons.
Just readability –if let nests less than amatch.
match x { Some(ref foo) => bar(foo), _ => (),}Use instead:
if let Some(ref foo) = x { bar(foo);}Checks for matches with two arms where anif let else willusually suffice.
Just readability –if let nests less than amatch.
Personal style preferences may differ.
Usingmatch:
match x { Some(ref foo) => bar(foo), _ => bar(&other_ref),}Usingif let withelse:
if let Some(ref foo) = x { bar(foo);} else { bar(&other_ref);}Checks for functions with method calls to.map(_) on an argof typeOption as the outermost expression.
Taking and returning anOption<T> may require additionalSome(_) andunwrap if all you have is aT.
fn double(param: Option<u32>) -> Option<u32> { param.map(|x| x * 2)}Use instead:
fn double(param: u32) -> u32 { param * 2}Checks forVec or array initializations that contain only one range.
This is almost always incorrect, as it will result in aVec that has only one element.Almost always, the programmer intended for it to include all elements in the range or forthe end of the range to be the length instead.
let x = [0..200];Use instead:
// If it was intended to include every element in the range...let x = (0..200).collect::<Vec<i32>>();// ...Or if 200 was meant to be the lenlet x = [0; 200];Detects expressions wheresize_of::<T> orsize_of_val::<T> is used as acount of elements of typeT
These functions expect a countofT and not a number of bytes
const SIZE: usize = 128;let x = [2u8; SIZE];let mut y = [2u8; SIZE];unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>() * SIZE) };Checks for calls tosize_of_val() where the argument isa reference to a reference.
Callingsize_of_val() with a reference to a reference as the argumentyields the size of the reference-type, not the size of the value behindthe reference.
struct Foo { buffer: [u8],}impl Foo { fn size(&self) -> usize { // Note that `&self` as an argument is a `&&Foo`: Because `self` // is already a reference, `&self` is a double-reference. // The return value of `size_of_val()` therefore is the // size of the reference-type, not the size of `self`. size_of_val(&self) }}Use instead:
struct Foo { buffer: [u8],}impl Foo { fn size(&self) -> usize { // Correct size_of_val(self) }}Checks for usage of_.skip_while(condition).next().
Readability, this can be written more concisely as_.find(!condition).
vec.iter().skip_while(|x| **x == 0).next();Use instead:
vec.iter().find(|x| **x != 0);Checks for string slices immediately followed byas_bytes.
It involves doing an unnecessary UTF-8 alignment check which is less efficient, and can cause a panic.
In some cases, the UTF-8 validation and potential panic from string slicing may be required forthe code’s correctness. If you need to ensure the slice boundaries fall on valid UTF-8 characterboundaries, the original form (s[1..5].as_bytes()) should be preferred.
let s = "Lorem ipsum";s[1..5].as_bytes();Use instead:
let s = "Lorem ipsum";&s.as_bytes()[1..5];Checks slow zero-filled vector initialization
These structures are non-idiomatic and less efficient than simply usingvec![0; len].
Specifically, forvec![0; len], the compiler can use a specialized type of allocationthat also zero-initializes the allocated memory in the same call(see:alloc_zeroed).
WritingVec::new() followed byvec.resize(len, 0) is suboptimal because,while it does do the same number of allocations,it involves two operations for allocating and initializing.Theresize call first allocates memory (sinceVec::new() did not), and onlythen zero-initializes it.
let mut vec1 = Vec::new();vec1.resize(len, 0);let mut vec2 = Vec::with_capacity(len);vec2.resize(len, 0);let mut vec3 = Vec::with_capacity(len);vec3.extend(repeat(0).take(len));Use instead:
let mut vec1 = vec![0; len];let mut vec2 = vec![0; len];let mut vec3 = vec![0; len];When sorting primitive values (integers, bools, chars, as wellas arrays, slices, and tuples of such items), it is typically better touse an unstable sort than a stable sort.
Typically, using a stable sort consumes more memory and cpu cycles.Because values which compare equal are identical, preserving theirrelative order (the guarantee that a stable sort provides) meansnothing, while the extra costs still apply.
As pointed out inissue #8241,a stable sort can instead be significantly faster for certain scenarios(eg. when a sorted vector is extended with new data and resorted).
For more information and benchmarking results, please refer to theissue linked above.
let mut vec = vec![2, 1, 3];vec.sort();Use instead:
let mut vec = vec![2, 1, 3];vec.sort_unstable();Finds items imported throughstd when available throughalloc.
Crates which haveno_std compatibility and require alloc may wish to ensure types are imported fromalloc to ensure disablingstd does not cause the crate to fail to compile. This lint is also usefulfor crates migrating to becomeno_std compatible.
use std::vec::Vec;Use instead:
use alloc::vec::Vec;Finds items imported throughstd when available throughcore.
Crates which haveno_std compatibility may wish to ensure types are imported from core to ensuredisablingstd does not cause the crate to fail to compile. This lint is also useful for cratesmigrating to becomeno_std compatible.
use std::hash::Hasher;Use instead:
use core::hash::Hasher;Checks for usages ofstr.trim().split("\n") andstr.trim().split("\r\n").
Hard-coding the line endings makes the code less compatible.str.lines should be used instead.
"some\ntext\nwith\nnewlines\n".trim().split('\n');Use instead:
"some\ntext\nwith\nnewlines\n".lines();This lint cannot detect if the split is intentionally restricted to a single type of newline ("\n" or"\r\n"), for example during the parsing of a specific file format in which precisely one newline type isvalid.
This lint checks for.to_string() method calls on values of type&str.
Theto_string method is also used on other types to convert them to a string.When called on a&str it turns the&str into the owned variantString, which can bemore specifically expressed with.to_owned().
let _ = "str".to_string();Use instead:
let _ = "str".to_owned();Checks for all instances ofx + _ wherex is of typeString, but only ifstring_add_assign doesnotmatch.
This particularAdd implementation is asymmetric (the other operand need not beString,butx does), while addition as mathematically defined is symmetric, andtheString::push_str(_) function is a perfectly good replacement.Therefore, some dislike it and wish not to have it in their code.
That said, other people think that string addition, having a long traditionin other languages is actually fine, which is why we decided to make thisparticular lintallow by default.
let x = "Hello".to_owned();x + ", World";Use instead:
let mut x = "Hello".to_owned();x.push_str(", World");Checks for string appends of the formx = x + y (withoutlet!).
It’s not really bad, but some people think that the.push_str(_) method is more readable.
let mut x = "Hello".to_owned();x = x + ", World";// More readablex += ", World";x.push_str(", World");Checks for the use of.extend(s.chars()) where s is a&str orString.
.push_str(s) is clearer
let abc = "abc";let def = String::from("def");let mut s = String::new();s.extend(abc.chars());s.extend(def.chars());The correct use would be:
let abc = "abc";let def = String::from("def");let mut s = String::new();s.push_str(abc);s.push_str(&def);Check if the string is transformed to byte array and casted back to string.
It’s unnecessary, the string can be used directly.
std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap();Use instead:
&"Hello World!"[6..11];Checks for theas_bytes method called on string literalsthat contain only ASCII characters.
Byte string literals (e.g.,b"foo") can be usedinstead. They are shorter but less discoverable thanas_bytes().
"str".as_bytes() and the suggested replacement ofb"str" are notequivalent because they have different types. The former is&[u8]while the latter is&[u8; 3]. That means in general they will have adifferent set of methods and different trait implementations.
fn f(v: Vec<u8>) {}f("...".as_bytes().to_owned()); // worksf(b"...".to_owned()); // does not work, because arg is [u8; 3] not Vec<u8>fn g(r: impl std::io::Read) {}g("...".as_bytes()); // worksg(b"..."); // does not workThe actual equivalent of"str".as_bytes() with the same type is notb"str" but&b"str"[..], which is a great deal of punctuation and notmore readable than a function call.
let bstr = "a byte string".as_bytes();Use instead:
let bstr = b"a byte string";Checks for<string_lit>.chars().any(|i| i == c).
It’s significantly slower than using a pattern instead, likematches!(c, '\\' | '.' | '+').
Despite this being faster, this is notperf as this is pretty common, and is a rather niceway to check if achar is any in a set. In any case, thisrestriction lint is availablefor situations where that additional performance is absolutely necessary.
"\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c);Use instead:
matches!(c, '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~');Checks for slice operations on strings
UTF-8 characters span multiple bytes, and it is easy to inadvertently confuse charactercounts and string indices. This may lead to panics, and should warrant some test casescontaining wide UTF-8 characters. This lint is most useful in code that should avoidpanics at all costs.
Probably lots of false positives. If an index comes from a known valid position (e.g.obtained viachar_indices over the same string), it is totally OK.
&"Ölkanne"[1..];Nothing. This lint has been deprecated
clippy:implicit_clone covers those cases.
Checks for usage oflibc::strlen on aCString orCStr value,and suggest callingas_bytes().len() orto_bytes().len() respectively instead.
This avoids calling an unsafelibc function.Currently, it also avoids calculating the length.
use std::ffi::CString;let cstring = CString::new("foo").expect("CString::new failed");let len = unsafe { libc::strlen(cstring.as_ptr()) };Use instead:
use std::ffi::CString;let cstring = CString::new("foo").expect("CString::new failed");let len = cstring.as_bytes().len();Checks for excessiveuse of bools in structs.
Excessive bools in a struct is often a sign thatthe type is being used to represent a statemachine, which is much better implemented as anenum.
The reason an enum is better for state machinesover structs is that enums more easily forbidinvalid states.
Structs with too many booleans may benefit from refactoringinto multi variant enums for better readability and API.
struct S { is_pending: bool, is_processing: bool, is_finished: bool,}Use instead:
enum S { Pending, Processing, Finished,}max-struct-bools: The maximum number of bool fields a struct can have
(default:3)
Detects struct fields that are prefixed or suffixedby the same characters or the name of the struct itself.
Information common to all struct fields is better represented in the struct name.
Characters with no casing will be considered when comparing prefixes/suffixesThis applies to numbers and non-ascii characters without casinge.g.foo1 andfoo2 is considered to have different prefixes(the prefixes arefoo1 andfoo2 respectively), as alsobar螃,bar蟹
struct Cake { cake_sugar: u8, cake_flour: u8, cake_eggs: u8}Use instead:
struct Cake { sugar: u8, flour: u8, eggs: u8}struct-field-name-threshold: The minimum number of struct fields for the lints about field names to trigger
(default:3)
Looks for floating-point expressions thatcan be expressed using built-in methods to improve bothaccuracy and performance.
Negatively impacts accuracy and performance.
use std::f32::consts::E;let a = 3f32;let _ = (2f32).powf(a);let _ = E.powf(a);let _ = a.powf(1.0 / 2.0);let _ = a.log(2.0);let _ = a.log(10.0);let _ = a.log(E);let _ = a.powf(2.0);let _ = a * 2.0 + 4.0;let _ = if a < 0.0 { -a} else { a};let _ = if a < 0.0 { a} else { -a};is better expressed as
use std::f32::consts::E;let a = 3f32;let _ = a.exp2();let _ = a.exp();let _ = a.sqrt();let _ = a.log2();let _ = a.log10();let _ = a.ln();let _ = a.powi(2);let _ = a.mul_add(2.0, 4.0);let _ = a.abs();let _ = -a.abs();Lints for suspicious operations in impls of arithmetic operators, e.g.subtracting elements in an Add impl.
This is probably a typo or copy-and-paste error and not intended.
impl Add for Foo { type Output = Foo; fn add(self, other: Foo) -> Foo { Foo(self.0 - other.0) }}Checks for usage of the non-existent=*,=! and=-operators.
This is either a typo of*=,!= or-= orconfusing.
a =- 42; // confusing, should it be `a -= 42` or `a = -42`?Checks forCommand::arg() invocations that look like theyshould be multiple arguments instead, such asarg("-t ext2").
Command::arg() does not split arguments by space. An argument likearg("-t ext2")will be passed as a single argument to the command,which is likely not what was intended.
std::process::Command::new("echo").arg("-n hello").spawn().unwrap();Use instead:
std::process::Command::new("echo").args(["-n", "hello"]).spawn().unwrap();Detects the use of outer doc comments (///,/**) followed by a bang (!):///!
Triple-slash comments (known as “outer doc comments”) apply to items that follow it.An outer doc comment followed by a bang (i.e.///!) has no specific meaning.
The user most likely meant to write an inner doc comment (//!,/*!), whichapplies to the parent item (i.e. the item that the comment is contained in,usually a module or crate).
Inner doc comments can only appear before items, so there are certain cases where the suggestionmade by this lint is not valid code. For example:
fn foo() {}///!fn bar() {}This lint detects the doc comment and suggests changing it to//!, but an inner doc commentis not valid at that position.
In this example, the doc comment is attached to thefunction, rather than themodule.
pub mod util { ///! This module contains utility functions. pub fn dummy() {}}Use instead:
pub mod util { //! This module contains utility functions. pub fn dummy() {}}Checks for formatting ofelse. It lints if theelseis followed immediately by a newline or theelse seems to be missing.
This is probably some refactoring remnant, even if thecode is correct, it might look confusing.
if foo {} { // looks like an `else` is missing here}if foo {} if bar { // looks like an `else` is missing here}if foo {} else{ // this is the `else` block of the previous `if`, but should it be?}if foo {} elseif bar { // this is the `else` block of the previous `if`, but should it be?}Checks for calls tomap followed by acount.
It looks suspicious. Maybemap was confused withfilter.If themap call is intentional, this should be rewrittenusinginspect. Or, if you intend to drive the iterator tocompletion, you can just usefor_each instead.
let _ = (0..3).map(|x| x + 2).count();Lints for suspicious operations in impls of OpAssign, e.g.subtracting elements in an AddAssign impl.
This is probably a typo or copy-and-paste error and not intended.
impl AddAssign for Foo { fn add_assign(&mut self, other: Foo) { *self = *self - other; }}Checks for the suspicious use ofOpenOptions::create()without an explicitOpenOptions::truncate().
create() alone will either create a new file or open anexisting file. If the file already exists, it will beoverwritten when written to, but the file will not betruncated by default.If less data is written to the filethan it already contains, the remainder of the file willremain unchanged, and the end of the file will contain olddata.In most cases, one should either usecreate_new to ensurethe file is created from scratch, or ensuretruncate iscalled so that the truncation behaviour is explicit.truncate(true)will ensure the file is entirely overwritten with new data, whereastruncate(false) will explicitly keep the default behavior.
use std::fs::OpenOptions;OpenOptions::new().create(true);Use instead:
use std::fs::OpenOptions;OpenOptions::new().create(true).truncate(true);Checks for unlikely usages of binary operators that are almostcertainly typos and/or copy/paste errors, given the other usagesof binary operators nearby.
They are probably bugs and if they aren’t then they look like bugsand you should add a comment explaining why you are doing such anodd set of operations.
There may be some false positives if you are trying to do somethingunusual that happens to look like a typo.
struct Vec3 { x: f64, y: f64, z: f64,}impl Eq for Vec3 {}impl PartialEq for Vec3 { fn eq(&self, other: &Self) -> bool { // This should trigger the lint because `self.x` is compared to `other.y` self.x == other.y && self.y == other.y && self.z == other.z }}Use instead:
// same as above except:impl PartialEq for Vec3 { fn eq(&self, other: &Self) -> bool { // Note we now compare other.x to self.x self.x == other.x && self.y == other.y && self.z == other.z }}Checks for calls to [splitn](https://doc.rust-lang.org/std/primitive.str.html#method.splitn) andrelated functions with either zero or one splits.
These calls don’t actually split the value and arelikely to be intended as a different number.
for x in s.splitn(1, ":") { // ..}Use instead:
for x in s.splitn(2, ":") { // ..}Checks for the usage of_.to_owned(), on aCow<'_, _>.
Callingto_owned() on aCow creates a clone of theCowitself, without taking ownership of theCow contents (i.e.it’s equivalent to callingCow::clone).The similarly namedinto_owned method, on the other hand,clones theCow contents, effectively turning anyCow::Borrowedinto aCow::Owned.
Given the potential ambiguity, consider replacingto_ownedwithclone for better readability or, if getting aCow::Ownedwas the original intent, usinginto_owned instead.
let s = "Hello world!";let cow = Cow::Borrowed(s);let data = cow.to_owned();assert!(matches!(data, Cow::Borrowed(_)))Use instead:
let s = "Hello world!";let cow = Cow::Borrowed(s);let data = cow.clone();assert!(matches!(data, Cow::Borrowed(_)))or
let s = "Hello world!";let cow = Cow::Borrowed(s);let _data: String = cow.into_owned();Checks the formatting of a unary operator on the right hand sideof a binary operator. It lints if there is no space between the binary and unary operators,but there is a space between the unary and its operand.
This is either a typo in the binary operator or confusing.
// &&! looks like a different operatorif foo &&! bar {}Use instead:
if foo && !bar {}Warns for a Bitwise XOR (^) operator being probably confused as a powering. It will not trigger if any of the numbers are not in decimal.
It’s most probably a typo and may lead to unexpected behaviours.
let x = 3_i32 ^ 4_i32;Use instead:
let x = 3_i32.pow(4);Checks for calls tocore::mem::swap where either parameter is derived from a pointer
When at least one parameter toswap is derived from a pointer it may overlap with theother. This would then lead to undefined behavior.
unsafe fn swap(x: &[*mut u32], y: &[*mut u32]) { for (&x, &y) in x.iter().zip(y) { core::mem::swap(&mut *x, &mut *y); }}Use instead:
unsafe fn swap(x: &[*mut u32], y: &[*mut u32]) { for (&x, &y) in x.iter().zip(y) { core::ptr::swap(x, y); }}Checks for usage ofstd::mem::swap with temporary values.
Storing a new value in place of a temporary value which willbe dropped right after theswap is an inefficient way of performingan assignment. The same result can be achieved by using a regularassignment.
fn replace_string(s: &mut String) { std::mem::swap(s, &mut String::from("replaced"));}Use instead:
fn replace_string(s: &mut String) { *s = String::from("replaced");}Also, swapping two temporary values has no effect, as they willboth be dropped right after swapping them. This is likely an indicationof a bug. For example, the following code swaps the references tothe last element of the vectors, instead of swapping the elementsthemselves:
fn bug(v1: &mut [i32], v2: &mut [i32]) { // Incorrect: swapping temporary references (`&mut &mut` passed to swap) std::mem::swap(&mut v1.last_mut().unwrap(), &mut v2.last_mut().unwrap());}Use instead:
fn correct(v1: &mut [i32], v2: &mut [i32]) { std::mem::swap(v1.last_mut().unwrap(), v2.last_mut().unwrap());}Checks doc comments for usage of tab characters.
The rust style-guide promotes spaces instead of tabs for indentation.To keep a consistent view on the source, also doc comments should not have tabs.Also, explaining ascii-diagrams containing tabs can get displayed incorrectly when thedisplay settings of the author and reader differ.
////// Struct to hold two strings:/// - firstone/// - secondonepub struct DoubleString { /// /// - First String: /// - needs to be inside here first_string: String, /// /// - Second String: /// - needs to be inside here second_string: String,}Will be converted to:
////// Struct to hold two strings:/// - first one/// - second onepub struct DoubleString { /// /// - First String: /// - needs to be inside here first_string: String, /// /// - Second String: /// - needs to be inside here second_string: String,}Checks for construction of a structure or tuple just toassign a value in it.
Readability. If the structure is only created to beupdated, why not write the structure you want in the first place?
(0, 0).0 = 1Checks for#[test] in doctests unless they are marked witheitherignore,no_run orcompile_fail.
Code in examples marked as#[test] will somewhatsurprisingly not be run bycargo test. If you really wantto show how to test stuff in an example, mark itno_run tomake the intent clear.
/// An example of a doctest with a `main()` function////// # Examples////// ```/// #[test]/// fn equality_works() {/// assert_eq!(1_u8, 1);/// }/// ```fn test_attr_in_doctest() { unimplemented!();}Triggers when a testing function (marked with the#[test] attribute) isn’t inside a testing module(marked with#[cfg(test)]).
The idiomatic (and more performant) way of writing tests is inside a testing module (flagged with#[cfg(test)]),having test functions outside of this module is confusing and may lead to them being “hidden”.
#[test]fn my_cool_test() { // [...]}#[cfg(test)]mod tests { // [...]}Use instead:
#[cfg(test)]mod tests { #[test] fn my_cool_test() { // [...] }}Checks for.to_digit(..).is_some() onchars.
This is a convoluted way of checking if achar is a digit. It’smore straight forward to use the dedicatedis_digit method.
let is_digit = c.to_digit(radix).is_some();can be written as:
let is_digit = c.is_digit(radix);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks forToString::to_stringapplied to a type that implementsDisplayin a macro that does formatting.
Since the type implementsDisplay, the use ofto_string isunnecessary.
println!("error: something failed at {}", Location::caller().to_string());Use instead:
println!("error: something failed at {}", Location::caller());Checks for direct implementations ofToString.
This trait is automatically implemented for any type which implements theDisplay trait.As such,ToString shouldn’t be implemented directly:Display should be implemented instead,and you get theToString implementation for free.
struct Point { x: usize, y: usize,}impl ToString for Point { fn to_string(&self) -> String { format!("({}, {})", self.x, self.y) }}Use instead:
struct Point { x: usize, y: usize,}impl std::fmt::Display for Point { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "({}, {})", self.x, self.y) }}Checks for usage oftodo!.
Thetodo! macro indicates the presence of unfinished code,so it should not be present in production code.
todo!();Finish the implementation, or consider marking it as explicitly unimplemented.
unimplemented!();Checks if the first paragraph in the documentation of items listed in the module page is too long.
Documentation will show the first paragraph of the docstring in the summary page of amodule. Having a nice, short summary in the first paragraph is part of writing good docs.
/// A very short summary./// A much longer explanation that goes into a lot more detail about/// how the thing works, possibly with doclinks and so one,/// and probably spanning a many rows.struct Foo {}Use instead:
/// A very short summary.////// A much longer explanation that goes into a lot more detail about/// how the thing works, possibly with doclinks and so one,/// and probably spanning a many rows.struct Foo {}Checks for functions with too many parameters.
Functions with lots of parameters are considered badstyle and reduce readability (“what does the 5th parameter mean?”). Considergrouping some parameters into a new type.
fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) { // ..}too-many-arguments-threshold: The maximum number of argument a function or method can have
(default:7)
Checks for functions with a large amount of lines.
Functions with a lot of lines are harder to understanddue to having to look at a larger amount of code to understand what thefunction is doing. Consider splitting the body of the function intomultiple functions.
fn im_too_long() { println!(""); // ... 100 more LoC println!("");}too-many-lines-threshold: The maximum number of lines a function or method can have
(default:100)
Checks for function arguments and let bindings denoted asref.
Theref declaration makes the function take an ownedvalue, but turns the argument into a reference (which means that the valueis destroyed when exiting the function). This adds not much value: eithertake a reference type, or take an owned value and create references in thebody.
For let bindings,let x = &foo; is preferred overlet ref x = foo. Thetype ofx is more obvious with the former.
If the argument is dereferenced within the function,removing theref will lead to errors. This can be fixed by removing thedereferences, e.g., changing*x tox within the function.
fn foo(ref _x: u8) {}Use instead:
fn foo(_x: &u8) {}Displays a warning when a struct with a trailing zero-sized array is declared without arepr attribute.
Zero-sized arrays aren’t very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters (for example, in conjunction with manual allocation to make it easy to compute the offset of the array). Either way,#[repr(C)] (or anotherrepr attribute) is needed.
struct RarelyUseful { some_field: u32, last: [u32; 0],}Use instead:
#[repr(C)]struct MoreOftenUseful { some_field: usize, last: [u32; 0],}Checks for cases where generics or trait objects are being used and multiplesyntax specifications for trait bounds are used simultaneously.
Duplicate bounds makes the codeless readable than specifying them only once.
fn func<T: Clone + Default>(arg: T) where T: Clone + Default {}Use instead:
fn func<T: Clone + Default>(arg: T) {}// orfn func<T>(arg: T) where T: Clone + Default {}fn foo<T: Default + Default>(bar: T) {}Use instead:
fn foo<T: Default>(bar: T) {}fn foo<T>(bar: T) where T: Default + Default {}Use instead:
fn foo<T>(bar: T) where T: Default {}Checks for transmutes from a&[u8] to a&str.
Not every byte slice is a valid UTF-8 string.
from_utf8 which this lint suggests using is slower thantransmuteas it needs to validate the input.If you are certain that the input is always a valid UTF-8,usefrom_utf8_unchecked which is as fast astransmutebut has a semantically meaningful name.from_utf8 instead of callingunwrap.let b: &[u8] = &[1_u8, 2_u8];unsafe { let _: &str = std::mem::transmute(b); // where b: &[u8]}// should be:let _ = std::str::from_utf8(b).unwrap();Checks for transmutes from an integer to abool.
This might result in an invalid in-memory representation of abool.
let x = 1_u8;unsafe { let _: bool = std::mem::transmute(x); // where x: u8}// should be:let _: bool = x != 0;Checks for transmutes fromT toNonZero<T>, and suggests thenew_uncheckedmethod instead.
Transmutes work on any types and thus might cause unsoundness when those types changeelsewhere.new_unchecked only works for the appropriate types instead.
let _: NonZero<u32> = unsafe { std::mem::transmute(123) };Use instead:
let _: NonZero<u32> = unsafe { NonZero::new_unchecked(123) };Checks for null function pointer creation through transmute.
Creating a null function pointer is undefined behavior.
More info: https://doc.rust-lang.org/nomicon/ffi.html#the-nullable-pointer-optimization
Not all cases can be detected at the moment of this writing.For example, variables which hold a null pointer and are then fed to atransmutecall, aren’t detectable yet.
let null_fn: fn() = unsafe { std::mem::transmute( std::ptr::null::<()>() ) };Use instead:
let null_fn: Option<fn()> = None;Checks for transmutes from a pointer to a pointer, orfrom a reference to a reference.
Transmutes are dangerous, and these can instead bewritten as casts.
let ptr = &1u32 as *const u32;unsafe { // pointer-to-pointer transmute let _: *const f32 = std::mem::transmute(ptr); // ref-ref transmute let _: &f32 = std::mem::transmute(&1u32);}// These can be respectively written:let _ = ptr as *const f32;let _ = unsafe{ &*(&1u32 as *const u32 as *const f32) };Checks for transmutes from a pointer to a reference.
This can always be rewritten with& and*.
mem::transmute in statics and constants is stable from Rust 1.46.0,while dereferencing raw pointer is not stable yet.If you need to do this in those places,you would have to usetransmute instead.unsafe { let _: &T = std::mem::transmute(p); // where p: *const T}// can be written:let _: &T = &*p;msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for transmutes between types which do not have a representation defined relative toeach other.
The results of such a transmute are not defined.
This lint has had multiple problems in the past and was moved tonursery. See issue#8496 for more details.
struct Foo<T>(u32, T);let _ = unsafe { core::mem::transmute::<Foo<u32>, Foo<i32>>(Foo(0u32, 0u32)) };Use instead:
#[repr(C)]struct Foo<T>(u32, T);let _ = unsafe { core::mem::transmute::<Foo<u32>, Foo<i32>>(Foo(0u32, 0u32)) };Checks for transmutes that could be a pointer cast.
Readability. The code tricks people into thinking thatsomething complex is going on.
unsafe { std::mem::transmute::<*const [i32], *const [u16]>(p) };Use instead:
p as *const [u16];Checks for transmute calls which would receive a null pointer.
Transmuting a null pointer is undefined behavior.
Not all cases can be detected at the moment of this writing.For example, variables which hold a null pointer and are then fed to atransmutecall, aren’t detectable yet.
let null_ref: &u64 = unsafe { std::mem::transmute(0 as *const u64) };Warns about callingstr::trim (or variants) beforestr::split_whitespace.
split_whitespace already ignores leading and trailing whitespace.
" A B C ".trim().split_whitespace();Use instead:
" A B C ".split_whitespace();Checks for trivialregexcreation (withRegex::new,RegexBuilder::new, orRegexSet::new).
Matching the regex can likely be replaced by== orstr::starts_with,str::ends_with orstd::contains or otherstrmethods.
If the same regex is going to be applied to multipleinputs, the precomputations done byRegex construction can givesignificantly better performance than any of thestr-based methods.
Regex::new("^foobar")Use instead:
str::starts_with("foobar")Checks for functions taking arguments by reference, wherethe argument type isCopy and small enough to be more efficient to alwayspass by value.
In many calling conventions instances of structs willbe passed through registers if they fit into two or less general purposeregisters.
This lint is target dependent, some cases will lint on 64-bit targets butnot 32-bit or lower targets.
The configuration optiontrivial_copy_size_limit can be set to overridethis limit for a project.
This lint attempts to allow passing arguments by reference if a referenceto that argument is returned. This is implemented by comparing the lifetimeof the argument and return value for equality. However, this can causefalse positives in cases involving multiple lifetimes that are bounded byeach other.
Also, it does not take account of other similar cases where getting memory addressesmatters; namely, returning the pointer to the argument in question,and passing the argument, as both references and pointers,to a function that needs the memory address. For further details, refer tothis issuethat explains a real case in which this false positiveled to anundefined behavior introduced with unsafe code.
fn foo(v: &u32) {}Use instead:
fn foo(v: u32) {}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
trivial-copy-size-limit: The maximum size (in bytes) to consider aCopy type for passing by value instead of byreference.
(default:target_pointer_width)
Checks for usage ofErr(x)?.
The? operator is designed to allow calls thatcan fail to be easily chained. For example,foo()?.bar() orfoo(bar()?). BecauseErr(x)? can’t be used that way (it willalways return), it is more clear to writereturn Err(x).
fn foo(fail: bool) -> Result<i32, String> { if fail { Err("failed")?; } Ok(0)}Could be written:
fn foo(fail: bool) -> Result<i32, String> { if fail { return Err("failed".into()); } Ok(0)}Checks for tuple<=>array conversions that are not done with.into().
It may be unnecessary complexity..into() works for converting tuples<=> arrays of up to12 elements and conveys the intent more clearly, while also leaving less room for hard tospot bugs!
The suggested code may hide potential asymmetry in some cases. See#11085 for more info.
let t1 = &[(1, 2), (3, 4)];let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect();Use instead:
let t1 = &[(1, 2), (3, 4)];let v1: Vec<[u32; 2]> = t1.iter().map(|&t| t.into()).collect();msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for types used in structs, parameters andletdeclarations above a certain complexity threshold.
Too complex types make the code less readable. Considerusing atype definition to simplify them.
struct PointMatrixContainer { matrix: Rc<Vec<Vec<Box<(u32, u32, u32, u32)>>>>,}fn main() { let point_matrix: Vec<Vec<Box<(u32, u32, u32, u32)>>> = vec![ vec![ Box::new((1, 2, 3, 4)), Box::new((5, 6, 7, 8)), ], vec![ Box::new((9, 10, 11, 12)), ], ]; let shared_point_matrix: Rc<Vec<Vec<Box<(u32, u32, u32, u32)>>>> = Rc::new(point_matrix); let container = PointMatrixContainer { matrix: shared_point_matrix, }; // ...}Use instead:
type PointMatrix = Vec<Vec<Box<(u32, u32, u32, u32)>>>;type SharedPointMatrix = Rc<PointMatrix>;struct PointMatrixContainer { matrix: SharedPointMatrix,}fn main() { let point_matrix: PointMatrix = vec![ vec![ Box::new((1, 2, 3, 4)), Box::new((5, 6, 7, 8)), ], vec![ Box::new((9, 10, 11, 12)), ], ]; let shared_point_matrix: SharedPointMatrix = Rc::new(point_matrix); let container = PointMatrixContainer { matrix: shared_point_matrix, }; // ...}type-complexity-threshold: The maximum complexity a type can have
(default:250)
Looks for calls to.type_id() on aBox<dyn _>.
This almost certainly does not do what the user expects and can lead to subtle bugs.Calling.type_id() on aBox<dyn Trait> returns a fixedTypeId of theBox itself,rather than returning theTypeId of the underlying type behind the trait object.
ForBox<dyn Any> specifically (and trait objects that haveAny as its supertrait),this lint will provide a suggestion, which is to dereference the receiver explicitlyto go fromBox<dyn Any> todyn Any.This makes sure that.type_id() resolves to a dynamic call on the trait objectand not on the box.
If the fixedTypeId of theBox is the intended behavior, it’s better to be explicit about itand writeTypeId::of::<Box<dyn Trait>>():this makes it clear that a fixedTypeId is returned and not theTypeId of the implementor.
use std::any::{Any, TypeId};let any_box: Box<dyn Any> = Box::new(42_i32);assert_eq!(any_box.type_id(), TypeId::of::<i32>()); // ⚠️ this fails!Use instead:
use std::any::{Any, TypeId};let any_box: Box<dyn Any> = Box::new(42_i32);assert_eq!((*any_box).type_id(), TypeId::of::<i32>());// ^ dereference first, to call `type_id` on `dyn Any`This lint warns about unnecessary type repetitions in trait bounds
Repeating the type for every bound makes the codeless readable than combining the bounds
pub fn foo<T>(t: T) where T: Copy, T: Clone {}Use instead:
pub fn foo<T>(t: T) where T: Copy + Clone {}max-trait-bounds: The maximum number of bounds a trait can have to be linted
(default:3)
msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for calls toRead::bytes on types which don’t implementBufRead.
The default implementation callsread for each byte, which can be very inefficient for data that’s not in memory, such asFile.
use std::io::Read;use std::fs::File;let file = File::open("./bytes.txt").unwrap();file.bytes();Use instead:
use std::io::{BufReader, Read};use std::fs::File;let file = BufReader::new(File::open("./bytes.txt").unwrap());file.bytes();Lints subtraction between anInstant and aDuration, or between twoDuration values.
Unchecked subtraction could cause underflow on certain platforms, leading tounintentional panics.
let time_passed = Instant::now() - Duration::from_secs(5);let dur1 = Duration::from_secs(3);let dur2 = Duration::from_secs(5);let diff = dur1 - dur2;Use instead:
let time_passed = Instant::now().checked_sub(Duration::from_secs(5));let dur1 = Duration::from_secs(3);let dur2 = Duration::from_secs(5);let diff = dur1.checked_sub(dur2);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks that there isn’t an infinite recursion in traitimplementations.
Infinite recursion in trait implementation will either cause crashesor result in an infinite loop, and it is hard to detect.
enum Foo { A, B,}impl PartialEq for Foo { fn eq(&self, other: &Self) -> bool { self == other // bad! }}Use instead:
#[derive(PartialEq)]enum Foo { A, B,}As an alternative, rewrite the logic without recursion:
enum Foo { A, B,}impl PartialEq for Foo { fn eq(&self, other: &Self) -> bool { matches!((self, other), (Foo::A, Foo::A) | (Foo::B, Foo::B)) }}Checks forunsafe blocks and impls without a// SAFETY: commentexplaining why the unsafe operations performed insidethe block are safe.
Note the comment must appear on the line(s) preceding the unsafe blockwith nothing appearing in between. The following is ok:
foo( // SAFETY: // This is a valid safety comment unsafe { *x })But neither of these are:
// SAFETY:// This is not a valid safety commentfoo( /* SAFETY: Neither is this */ unsafe { *x },);Undocumented unsafe blocks and impls can make it difficult to read and maintain code.Writing out the safety justification may help in discovering unsoundness or bugs.
use std::ptr::NonNull;let a = &mut 42;let ptr = unsafe { NonNull::new_unchecked(a) };Use instead:
use std::ptr::NonNull;let a = &mut 42;// SAFETY: references are guaranteed to be non-null.let ptr = unsafe { NonNull::new_unchecked(a) };accept-comment-above-attributes: Whether to accept a safety comment to be placed above the attributes for theunsafe block
(default:true)
accept-comment-above-statement: Whether to accept a safety comment to be placed above the statement containing theunsafe block
(default:true)
Checks for string literals that contain Unicode in a formthat is not equal to itsNFC-recomposition.
If such a string is compared to another, the resultsmay be surprising.
You may not see it, but “à”“ and “à”“ aren’t the same string. Theformer when escaped is actually"a\u{300}" while the latter is"\u{e0}".
Checks for usage ofunimplemented!.
This macro, or panics in general, may be unwanted in production code.
unimplemented!();It detects references to uninhabited types, such as! andwarns when those are either dereferenced or returned from a function.
Dereferencing a reference to an uninhabited type would createan instance of such a type, which cannot exist. This constitutesundefined behaviour. Such a reference could have been createdbyunsafe code.
The following function can return a reference to an uninhabited type(Infallible) because it usesunsafe code to create it. However,the user of such a function could dereference the return value andtrigger an undefined behavior from safe code.
fn create_ref() -> &'static std::convert::Infallible { unsafe { std::mem::transmute(&()) }}Checks forMaybeUninit::uninit().assume_init().
For most types, this is undefined behavior.
For now, we accept empty tuples and tuples / arraysofMaybeUninit. There may be other types that allow uninitializeddata, but those are not yet rigorously defined.
// Beware the UBuse std::mem::MaybeUninit;let _: usize = unsafe { MaybeUninit::uninit().assume_init() };Note that the following is OK:
use std::mem::MaybeUninit;let _: [MaybeUninit<bool>; 5] = unsafe { MaybeUninit::uninit().assume_init()};Checks forset_len() call that createsVec with uninitialized elements.This is commonly caused by callingset_len() right after allocating orreserving a buffer withnew(),default(),with_capacity(), orreserve().
It creates aVec with uninitialized data, which leads toundefined behavior with most safe operations. Notably, uninitializedVec<u8> must not be used with genericRead.
Moreover, callingset_len() on aVec created withnew() ordefault()creates out-of-bound values that lead to heap memory corruption when used.
This lint only checks directly adjacent statements.
let mut vec: Vec<u8> = Vec::with_capacity(1000);unsafe { vec.set_len(1000); }reader.read(&mut vec); // undefined behavior!let mut vec: Vec<u8> = vec![0; 1000];reader.read(&mut vec);MaybeUninit:let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);vec.set_len(1000); // `MaybeUninit` can be uninitializedVec::spare_capacity_mut() is available:let mut vec: Vec<u8> = Vec::with_capacity(1000);let remaining = vec.spare_capacity_mut(); // `&mut [MaybeUninit<u8>]`// perform initialization with `remaining`vec.set_len(...); // Safe to call `set_len()` on initialized partDetect when a variable is not inlined in a format string,and suggests to inline it.
Non-inlined code is slightly more difficult to read and understand,as it requires arguments to be matched against the format string.The inlined syntax, where allowed, is simpler.
format!("{}", var);format!("{:?}", var);format!("{v:?}", v = var);format!("{0} {0}", var);format!("{0:1$}", var, width);format!("{:.*}", prec, var);Use instead:
format!("{var}");format!("{var:?}");format!("{var:?}");format!("{var} {var}");format!("{var:width$}");format!("{var:.prec$}");Ifallow-mixed-uninlined-format-args is set tofalse in clippy.toml,the following code will also trigger the lint:
format!("{} {}", var, 1+2);Use instead:
format!("{var} {}", 1+2);If a format string contains a numbered argument that cannot be inlinednothing will be suggested, e.g.println!("{0}={1}", var, 1+2).
allow-mixed-uninlined-format-args: Whether to allow mixed uninlined format args, e.g.format!("{} {}", a, foo.bar)
(default:true)
msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for passing a unit value as an argument to a function without using aunit literal (()).
This is likely the result of an accidental semicolon.
foo({ let a = bar(); baz(a);})Checks for comparisons to unit. This includes all binarycomparisons (like== and<) and asserts.
Unit is always equal to itself, and thus is just aclumsily written constant. Mostly this happens when someone accidentallyadds semicolons at the end of the operands.
if { foo();} == { bar();} { baz();}is equal to
{ foo(); bar(); baz();}For asserts:
assert_eq!({ foo(); }, { bar(); });will always succeed
Detects().hash(_).
Hashing a unit value doesn’t do anything as the implementation ofHash for() is a no-op.
match my_enum {Empty => ().hash(&mut state),WithValue(x) => x.hash(&mut state),}Use instead:
match my_enum {Empty => 0_u8.hash(&mut state),WithValue(x) => x.hash(&mut state),}Checks for functions that expect closures of typeFn(…) -> Ord where the implemented closure returns the unit type.The lint also suggests to remove the semi-colon at the end of the statement if present.
Likely, returning the unit type is unintentional, andcould simply be caused by an extra semi-colon. Since () implements Ordit doesn’t cause a compilation error.This is the same reasoning behind the unit_cmp lint.
If returning unit is intentional, then there is noway of specifying this without triggering needless_return lint
let mut twins = vec![(1, 1), (2, 2)];twins.sort_by_key(|x| { x.1; });Checks for a return type containing aBox<T> whereT implementsSized
The lint ignoresBox<T> whereT is larger thanunnecessary_box_size,as returning a largeT directly may be detrimental to performance.
It’s better to just returnT in these cases. The caller may not needthe value to be boxed, and it’s expensive to free the memory once theBox<T> been dropped.
fn foo() -> Box<String> { Box::new(String::from("Hello, world!"))}Use instead:
fn foo() -> String { String::from("Hello, world!")}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
unnecessary-box-size: The byte size aT inBox<T> can have, below which it triggers theclippy::unnecessary_box lint
(default:128)
Checks for casts to the same type, casts of int literals to integertypes, casts of float literals to float types, and casts between rawpointers that don’t change type or constness.
It’s just unnecessary.
When the expression on the left is a function call, the lint considersthe return type to be a type alias if it’s aliased through ausestatement (likeuse std::io::Result as IoResult). It will not lintsuch cases.
This check will only work on primitive types without any intermediatereferences: raw pointers and trait objects may or may not work.
let _ = 2i32 as i32;let _ = 0.5 as f32;Better:
let _ = 2_i32;let _ = 0.5_f32;Checks for#[cfg_attr(clippy, allow(clippy::lint))]and suggests to replace it with#[allow(clippy::lint)].
There is no reason to put clippy attributes behind a clippycfg as they are notrun by anything else than clippy.
#![cfg_attr(clippy, allow(clippy::deprecated_cfg_attr))]Use instead:
#![allow(clippy::deprecated_cfg_attr)]Checks forDebug formatting ({:?}) applied to anOsStr orPath.
Rust doesn’t guarantee whatDebug formatting looks like, and it couldchange in the future.OsStrs andPaths can beDisplay formattedusing theirdisplay methods.
Furthermore, withDebug formatting, certain characters are escaped.Thus, aDebug formattedPath is less likely to be clickable.
let path = Path::new("...");println!("The path is {:?}", path);Use instead:
let path = Path::new("…");println!("The path is {}", path.display());Checks for calls toTryInto::try_into andTryFrom::try_from when their infallible counterpartscould be used.
In those cases, theTryInto andTryFrom trait implementation is a blanket impl that forwardstoInto orFrom, which always succeeds.The returnedResult<_, Infallible> requires error handling to get the contained valueeven though the conversion can never fail.
let _: Result<i64, _> = 1i32.try_into();let _: Result<i64, _> = <_>::try_from(1i32);Usefrom/into instead:
let _: i64 = 1i32.into();let _: i64 = <_>::from(1i32);Checks forfilter_map calls that could be replaced byfilter ormap.More specifically it checks if the closure provided is only performing one of thefilter or map operations and suggests the appropriate option.
Complexity. The intent is also clearer if only a singleoperation is being performed.
let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None });// As there is no transformation of the argument this could be written as:let _ = (0..3).filter(|&x| x > 2);let _ = (0..4).filter_map(|x| Some(x + 1));// As there is no conditional check on the argument this could be written as:let _ = (0..4).map(|x| x + 1);Checks forfind_map calls that could be replaced byfind ormap. Morespecifically it checks if the closure provided is only performing one of thefind or map operations and suggests the appropriate option.
Complexity. The intent is also clearer if only a singleoperation is being performed.
let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None });// As there is no transformation of the argument this could be written as:let _ = (0..3).find(|&x| x > 2);let _ = (0..4).find_map(|x| Some(x + 1));// As there is no conditional check on the argument this could be written as:let _ = (0..4).map(|x| x + 1).next();Checks the usage of.first().is_some() or.first().is_none() to check if a slice isempty.
Using.is_empty() is shorter and better communicates the intention.
let v = vec![1, 2, 3];if v.first().is_none() { // The vector is empty...}Use instead:
let v = vec![1, 2, 3];if v.is_empty() { // The vector is empty...}Checks for usage offold when a more succinct alternative exists.Specifically, this checks forfolds which could be replaced byany,all,sum orproduct.
Readability.
(0..3).fold(false, |acc, x| acc || x > 2);Use instead:
(0..3).any(|x| x > 2);Checks the usage of.get().is_some() or.get().is_none() on std map types.
It can be done in one call with.contains()/.contains_key().
let s: HashSet<String> = HashSet::new();if s.get("a").is_some() { // code}Use instead:
let s: HashSet<String> = HashSet::new();if s.contains("a") { // code}Checks for usage of.collect::<Vec<String>>().join("") on iterators.
.collect::<String>() is more concise and might be more performant
let vector = vec!["hello", "world"];let output = vector.iter().map(|item| item.to_uppercase()).collect::<Vec<String>>().join("");println!("{}", output);The correct use would be:
let vector = vec!["hello", "world"];let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();println!("{}", output);While.collect::<String>() is sometimes more performant, there are cases whereusing.collect::<String>() over.collect::<Vec<String>>().join("")will prevent loop unrolling and will result in a negative performance impact.
Additionally, differences have been observed between aarch64 and x86_64 assembly output,with aarch64 tending to producing faster assembly in more cases when using.collect::<String>()
As the counterpart toor_fun_call, this lint looks for unnecessarylazily evaluated closures onOption andResult.
This lint suggests changing the following functions, when eager evaluation results insimpler code:
unwrap_or_else tounwrap_orand_then toandor_else toorget_or_insert_with toget_or_insertok_or_else took_orthen tothen_some (for msrv >= 1.62.0)Using eager evaluation is shorter and simpler in some cases.
It is possible, but not recommended forDeref andIndex to haveside effects. Eagerly evaluating them can change the semantics of the program.
let opt: Option<u32> = None;opt.unwrap_or_else(|| 42);Use instead:
let opt: Option<u32> = None;opt.unwrap_or(42);msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Detects functions that are written to return&str that could return&'static str but instead return a&'a str.
This leaves the caller unable to use the&str as&'static str, causing unnecessary allocations or confusion.This is also most likely what you meant to write.
impl MyType { fn returns_literal(&self) -> &str { "Literal" }}Use instead:
impl MyType { fn returns_literal(&self) -> &'static str { "Literal" }}Or, in case you may return a non-literalstr in future:
impl MyType { fn returns_literal<'a>(&'a self) -> &'a str { "Literal" }}Checks for.unwrap() related calls onResults andOptions that are constructed.
It is better to write the value directly without the indirection.
let val1 = Some(1).unwrap();let val2 = Ok::<_, ()>(1).unwrap();let val3 = Err::<(), _>(1).unwrap_err();Use instead:
let val1 = 1;let val2 = 1;let val3 = 1;Suggests removing the use of amap() (ormap_err()) method when anOption orResultis being constructed.
It introduces unnecessary complexity. Instead, the function can be called beforeconstructing theOption orResult from its return value.
Some(4).map(i32::swap_bytes)Use instead:
Some(i32::swap_bytes(4))Converts some constructs mapping an Enum value for equality comparison.
Calls such asopt.map_or(false, |val| val == 5) are needlessly long and cumbersome,and can be reduced to, for example,opt == Some(5) assumingopt implementsPartialEq.Also, calls such asopt.map_or(true, |val| val == 5) can be reduced toopt.is_none_or(|val| val == 5).This lint offers readability and conciseness improvements.
pub fn a(x: Option<i32>) -> (bool, bool) { ( x.map_or(false, |n| n == 5), x.map_or(true, |n| n > 5), )}Use instead:
pub fn a(x: Option<i32>) -> (bool, bool) { ( x == Some(5), x.is_none_or(|n| n > 5), )}Checks for unnecessary calls tomin() ormax() in the following cases
In the aforementioned cases it is not necessary to callmin() ormax()to compare values, it may even cause confusion.
let _ = 0.min(7_u32);Use instead:
let _ = 0;Detects passing a mutable reference to a function that onlyrequires an immutable reference.
The mutable reference rules out all other references tothe value. Also the code misleads about the intent of the call site.
vec.push(&mut value);Use instead:
vec.push(&value);Checks for expression statements that can be reduced to asub-expression.
Expressions by themselves often have no side-effects.Having such expressions reduces readability.
compute_array()[0];Checks for usage of.map_or_else() “map closure” forOption type.
This can be written more concisely by usingunwrap_or_else().
let k = 10;let x: Option<u32> = Some(4);let y = x.map_or_else(|| 2 * k, |n| n);Use instead:
let k = 10;let x: Option<u32> = Some(4);let y = x.unwrap_or_else(|| 2 * k);Detects cases of owned empty strings being passed as an argument to a function expecting&str
This results in longer and less readable code
vec!["1", "2", "3"].join(&String::new());Use instead:
vec!["1", "2", "3"].join("");Checks for usage of.map_or_else() “map closure” forResult type.
This can be written more concisely by usingunwrap_or_else().
let x: Result<u32, ()> = Ok(0);let y = x.map_or_else(|err| handle_error(err), |n| n);Use instead:
let x: Result<u32, ()> = Ok(0);let y = x.unwrap_or_else(|err| handle_error(err));Checks for// SAFETY: comments on safe code.
Safe code has no safety requirements, so there is no need todescribe safety invariants.
use std::ptr::NonNull;let a = &mut 42;// SAFETY: references are guaranteed to be non-null.let ptr = NonNull::new(a).unwrap();Use instead:
use std::ptr::NonNull;let a = &mut 42;let ptr = NonNull::new(a).unwrap();Checks for the doc comments of publicly visiblesafe functions and traits and warns if there is a# Safety section.
Safe functions and traits are safe to implement and therefore do notneed to describe safety preconditions that users are required to uphold.
/// # Safety////// This function should not be called before the horsemen are ready.pub fn start_apocalypse_but_safely(u: &mut Universe) { unimplemented!();}The function is safe, so there shouldn’t be any preconditionsthat have to be explained for safety reasons.
/// This function should really be documentedpub fn start_apocalypse(u: &mut Universe) { unimplemented!();}check-private-items: Whether to also run the listed lints on private items.
(default:false)
Checks for imports ending in::{self}.
In most cases, this can be written much more cleanly by omitting::{self}.
Removing::{self} will cause any non-module items at the same path to also be imported.This might cause a naming conflict (https://github.com/rust-lang/rustfmt/issues/3568). This lint makes no attemptto detect this scenario and that is why it is a restriction lint.
use std::io::{self};Use instead:
use std::io;Checks for the presence of a semicolon at the end ofamatch orif statement evaluating to().
The semicolon is not needed, and may be removed toavoid confusion and visual clutter.
if a > 10 { println!("a is greater than 10");};Use instead:
if a > 10 { println!("a is greater than 10");}Checks for usage ofVec::sort_by passing in a closurewhich compares the two arguments, either directly or indirectly.
It is more clear to useVec::sort_by_key (orVec::sort ifpossible) than to useVec::sort_by and a more complicatedclosure.
If the suggestedVec::sort_by_key uses Reverse and it isn’t alreadyimported by a use statement, then it will need to be added manually.
vec.sort_by(|a, b| a.foo().cmp(&b.foo()));Use instead:
vec.sort_by_key(|a| a.foo());Checks for initialization of an identicalstruct from another instanceof the type, either by copying a base without setting any field or bymoving all fields individually.
Readability suffers from unnecessary struct building.
struct S { s: String }let a = S { s: String::from("Hello, world!") };let b = S { ..a };Use instead:
struct S { s: String }let a = S { s: String::from("Hello, world!") };let b = a;The struct literalS { ..a } in the assignment tob could be replacedwith justa.
Has false positives when the base is a place expression that cannot bemoved out of, see#10547.
Empty structs are ignored by the lint.
Checks for unnecessary calls toToOwned::to_ownedand otherto_owned-like functions.
The unnecessary calls result in useless allocations.
unnecessary_to_owned can falsely trigger ifIntoIterator::into_iter is applied to anowned copy of a resource and the resource is later used mutably. See#8148.
let path = std::path::Path::new("x");foo(&path.to_string_lossy().to_string());fn foo(s: &str) {}Use instead:
let path = std::path::Path::new("x");foo(&path.to_string_lossy());fn foo(s: &str) {}Checks for calls ofunwrap[_err]() that cannot fail.
Usingif let ormatch is more idiomatic.
if option.is_some() { do_something_with(option.unwrap())}Could be written:
if let Some(value) = option { do_something_with(value)}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for private functions that only returnOk orSome.
It is not meaningful to wrap values when noNone orErr is returned.
There can be false positives if the function signature is designed tofit some external requirement.
fn get_cool_number(a: bool, b: bool) -> Option<i32> { if a && b { return Some(50); } if a { Some(0) } else { Some(10) }}Use instead:
fn get_cool_number(a: bool, b: bool) -> i32 { if a && b { return 50; } if a { 0 } else { 10 }}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Checks for structure field patterns bound to wildcards.
Using.. instead is shorter and leaves the focus onthe fields that are actually bound.
let f = Foo { a: 0, b: 0, c: 0 };match f { Foo { a: _, b: 0, .. } => {}, Foo { a: _, b: _, c: _ } => {},}Use instead:
let f = Foo { a: 0, b: 0, c: 0 };match f { Foo { b: 0, .. } => {}, Foo { .. } => {},}Checks for struct patterns that match against unit variant.
Struct pattern{ } or{ .. } is not needed for unit variant.
match Some(42) { Some(v) => v, None { .. } => 0,};// Ormatch Some(42) { Some(v) => v, None { } => 0,};Use instead:
match Some(42) { Some(v) => v, None => 0,};Checks for tuple patterns with a wildcardpattern (_) is next to a rest pattern (..).
NOTE: While_, .. means there is at least one element left,..means there are 0 or more elements left. This can make a differencewhen refactoring, but shouldn’t result in errors in the refactored code,since the wildcard pattern isn’t used anyway.
The wildcard pattern is unneeded as the rest patterncan match that element as well.
match t { TupleStruct(0, .., _) => (), _ => (),}Use instead:
match t { TupleStruct(0, ..) => (), _ => (),}Checks for unnested or-patterns, e.g.,Some(0) | Some(2) andsuggests replacing the pattern with a nested one,Some(0 | 2).
Another way to think of this is that it rewrites patterns indisjunctive normal form (DNF) intoconjunctive normal form (CNF).
In the example above,Some is repeated, which unnecessarily complicates the pattern.
fn main() { if let Some(0) | Some(2) = Some(0) {}}Use instead:
fn main() { if let Some(0 | 2) = Some(0) {}}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for usage ofunreachable!.
This macro, or panics in general, may be unwanted in production code.
unreachable!();Warns if a long integral or floating-point constant doesnot contain underscores.
Reading long numbers is difficult without separators.
61864918973511Use instead:
61_864_918_973_511unreadable-literal-lint-fractions: Should the fraction of a decimal be linted to include separators.
(default:true)
Checks for derivingserde::Deserialize on a type thathas methods usingunsafe.
Derivingserde::Deserialize will create a constructorthat may violate invariants held by another constructor.
use serde::Deserialize;#[derive(Deserialize)]pub struct Foo { // ..}impl Foo { pub fn new() -> Self { // setup here .. } pub unsafe fn parts() -> (&str, &str) { // assumes invariants hold }}Checks for imports that remove “unsafe” from an item’sname.
Renaming makes it less clear which traits andstructures are unsafe.
use std::cell::{UnsafeCell as TotallySafeCell};extern crate crossbeam;use crossbeam::{spawn_unsafe as spawn};Nothing. This lint has been deprecated
The suggested alternative could be substantially slower.
Warns if literal suffixes are not separated by anunderscore.To enforce unseparated literal suffix style,see theseparated_literal_suffix lint.
Suffix style should be consistent.
123832i32Use instead:
123832_i32Checks for transmutes between collections whosetypes have different ABI, size or alignment.
This is undefined behavior.
Currently, we cannot know whether a type is acollection, so we just lint the ones that come withstd.
// different size, therefore likely out-of-bounds memory access// You absolutely do not want this in your code!unsafe { std::mem::transmute::<_, Vec<u32>>(vec![2_u16])};You must always iterate, map and collect the values:
vec![2_u16].into_iter().map(u32::from).collect::<Vec<_>>();Nothing. This lint has been deprecated
Vec::as_mut_slice is now stable.
Nothing. This lint has been deprecated
Vec::as_slice is now stable.
Checks for functions that are declaredasync but have no.awaits inside of them.
Async functions with no async code create overhead, both mentally and computationally.Callers of async methods either need to be calling from an async function themselves or run it on an executor, both of whichcauses runtime overhead and hassle for the caller.
async fn get_random_number() -> i64 { 4 // Chosen by fair dice roll. Guaranteed to be random.}let number_future = get_random_number();Use instead:
fn get_random_number_improved() -> i64 { 4 // Chosen by fair dice roll. Guaranteed to be random.}let number_future = async { get_random_number_improved() };Nothing. This lint has been deprecated
Iterator::collect is now marked as#[must_use].
Checks for uses of theenumerate method where the index is unused (_)
The index from.enumerate() is immediately dropped.
let v = vec![1, 2, 3, 4];for (_, x) in v.iter().enumerate() { println!("{x}");}Use instead:
let v = vec![1, 2, 3, 4];for x in v.iter() { println!("{x}");}Detectsformatting parameters that have no effect on the output offormat!(),println!() or similar macros.
Shorter format specifiers are easier to read, it may also indicate thatan expected formatting operation such as adding padding isn’t happening.
println!("{:.}", 1.0);println!("not padded: {:5}", format_args!("..."));Use instead:
println!("{}", 1.0);println!("not padded: {}", format_args!("..."));// ORprintln!("padded: {:5}", format!("..."));Checks for unused written/read amount.
io::Write::write(_vectored) andio::Read::read(_vectored) are not guaranteed toprocess the entire buffer. They return how many bytes were processed, whichmight be smallerthan a given buffer’s length. If you don’t need to deal withpartial-write/read, usewrite_all/read_exact instead.
When working with asynchronous code (either with thefuturescrate or withtokio), a similar issue exists forAsyncWriteExt::write() andAsyncReadExt::read() : thesefunctions are also not guaranteed to process the entirebuffer. Your code should either handle partial-writes/reads, orcall thewrite_all/read_exact methods on those traits instead.
Detects only common patterns.
use std::io;fn foo<W: io::Write>(w: &mut W) -> io::Result<()> { w.write(b"foo")?; Ok(())}Use instead:
use std::io;fn foo<W: io::Write>(w: &mut W) -> io::Result<()> { w.write_all(b"foo")?; Ok(())}Checks for the creation of apeekable iterator that is never.peek()ed
Creating a peekable iterator without using any of its methods is likely a mistake,or just a leftover after a refactor.
let collection = vec![1, 2, 3];let iter = collection.iter().peekable();for item in iter { // ...}Use instead:
let collection = vec![1, 2, 3];let iter = collection.iter();for item in iter { // ...}Checks for calls toResult::ok() without using the returnedOption.
UsingResult::ok() may look like the result is checked likeunwrap orexpect would dobut it only silences the warning caused by#[must_use] on theResult.
some_function().ok();Use instead:
let _ = some_function();Detects cases where a whole-number literal float is being rounded, usingthefloor,ceil, orround methods.
This is unnecessary and confusing to the reader. Doing this is probably a mistake.
let x = 1f32.ceil();Use instead:
let x = 1f32;Checks methods that contain aself argument but don’t use it
It may be clearer to define the method as an associated function insteadof an instance method if it doesn’t requireself.
struct A;impl A { fn method(&self) {}}Could be written:
struct A;impl A { fn method() {}}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Checks foruse Trait where the Trait is only used for its methods and not referenced by a path directly.
Traits imported that aren’t used directly can be imported anonymously withuse Trait as _.It is more explicit, avoids polluting the current scope with unused names and can be useful to show which imports are required for traits.
use std::fmt::Write;fn main() { let mut s = String::new(); let _ = write!(s, "hello, world!"); println!("{s}");}Use instead:
use std::fmt::Write as _;fn main() { let mut s = String::new(); let _ = write!(s, "hello, world!"); println!("{s}");}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for unit (()) expressions that can be removed.
Such expressions add no value, but can make the codeless readable. Depending on formatting they can make abreak orreturnstatement look like a function call.
fn return_unit() -> () { ()}is equivalent to
fn return_unit() {}Warns if hexadecimal or binary literals are not groupedby nibble or byte.
Negatively impacts readability.
let x: u32 = 0xFFF_FFF;let y: u8 = 0b01_011_101;Checks for functions of typeResult that containexpect() orunwrap()
These functions promote recoverable errors to non-recoverable errors,which may be undesirable in code bases which wish to avoid panics,or be a bug in the specific function.
This can cause false positives in functions that handle both recoverable and non recoverable errors.
Before:
fn divisible_by_3(i_str: String) -> Result<(), String> { let i = i_str .parse::<i32>() .expect("cannot divide the input by three"); if i % 3 != 0 { Err("Number is not divisible by 3")? } Ok(())}After:
fn divisible_by_3(i_str: String) -> Result<(), String> { let i = i_str .parse::<i32>() .map_err(|e| format!("cannot divide the input by three: {}", e))?; if i % 3 != 0 { Err("Number is not divisible by 3")? } Ok(())}Checks for usages of the following functions with an argument that constructs a default value(e.g.,Default::default orString::new):
unwrap_orunwrap_or_elseor_insertor_insert_withReadability. Usingunwrap_or_default in place ofunwrap_or/unwrap_or_else, oror_defaultin place ofor_insert/or_insert_with, is simpler and more concise.
In some cases, the argument ofunwrap_or, etc. is needed for type inference. The lint uses aheuristic to try to identify such cases. However, the heuristic can produce false negatives.
x.unwrap_or(Default::default());map.entry(42).or_insert_with(String::new);Use instead:
x.unwrap_or_default();map.entry(42).or_default();Checks for.unwrap() or.unwrap_err() calls onResults and.unwrap() call onOptions.
It is better to handle theNone orErr case,or at least call.expect(_) with a more helpful message. Still, for a lot ofquick-and-dirty code,unwrap is a good choice, which is why this lint isAllow by default.
result.unwrap() will let the thread panic onErr values.Normally, you want to implement more sophisticated error handling,and propagate errors upwards with? operator.
Even if you want to panic on errors, not allErrors implement goodmessages on display. Therefore, it may be beneficial to look at the placeswhere they may get displayed. Activate this lint to do just that.
option.unwrap();result.unwrap();Use instead:
option.expect("more helpful message");result.expect("more helpful message");Ifexpect_used is enabled, instead:
option?;// orresult?;allow-unwrap-in-consts: Whetherunwrap should be allowed in code always evaluated at compile time
(default:true)
allow-unwrap-in-tests: Whetherunwrap should be allowed in test functions or#[cfg(test)]
(default:false)
Checks for fully capitalized names and optionally names containing a capitalized acronym.
In CamelCase, acronyms count as one word.Seenaming conventionsfor more.
By default, the lint only triggers on fully-capitalized names.You can use theupper-case-acronyms-aggressive: true config option to enable lintingon all camel case names
When two acronyms are contiguous, the lint can’t tell wherethe first acronym ends and the second starts, so it suggests to lowercase all ofthe letters in the second acronym.
struct HTTPResponse;Use instead:
struct HttpResponse;avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
upper-case-acronyms-aggressive: Enables verbose mode. Triggers if there is more than one uppercase char next to each other
(default:false)
Checks for usage ofDebug formatting. The purpose of thislint is to catch debugging remnants.
The purpose of theDebug trait is to facilitate debugging Rust code,andno guarantees are made about its output.It should not be used in user-facing output.
println!("{:?}", foo);Checks for unnecessary repetition of structure name when areplacement withSelf is applicable.
Unnecessary repetition. Mixed use ofSelf and structnamefeels inconsistent.
struct Foo;impl Foo { fn new() -> Foo { Foo {} }}could be
struct Foo;impl Foo { fn new() -> Self { Self {} }}msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
recursive-self-in-type-definitions: Whether the type itself in a struct or enum should be replaced withSelf when encountering recursive types.
(default:true)
Checks for the use of bindings with a single leadingunderscore.
A single leading underscore is usually used to indicatethat a binding will not be used. Using such a binding breaks thisexpectation.
The lint does not work properly with desugaring andmacro, it has been allowed in the meantime.
let _x = 0;let y = _x + 1; // Here we are using `_x`, even though it has a leading // underscore. We should rename `_x` to `x`Checks for the use of item with a single leadingunderscore.
A single leading underscore is usually used to indicatethat a item will not be used. Using such a item breaks thisexpectation.
fn _foo() {}struct _FooStruct {}fn main() { _foo(); let _ = _FooStruct{};}Use instead:
fn foo() {}struct FooStruct {}fn main() { foo(); let _ = FooStruct{};}Checks for usage of.as_ref() or.as_mut() where thetypes before and after the call are the same.
The call is unnecessary.
let x: &[i32] = &[1, 2, 3, 4, 5];do_stuff(x.as_ref());The correct use would be:
let x: &[i32] = &[1, 2, 3, 4, 5];do_stuff(x);Checks forextern crate anduse items annotated withlint attributes.
This lint permits lint attributes for lints emitted on the items themself.Foruse items these lints are:
Forextern crate items these lints are:
unused_imports on items with#[macro_use]Lint attributes have no effect on crate imports. Mostlikely a! was forgotten.
#[deny(dead_code)]extern crate foo;#[forbid(dead_code)]use foo::bar;Use instead:
#[allow(unused_imports)]use foo::baz;#[allow(unused_imports)]#[macro_use]extern crate baz;Checks that theconcat! macro has at least two arguments.
If there are less than 2 arguments, then calling the macro is doing nothing.
let x = concat!("a");Use instead:
let x = "a";Checks forInto,TryInto,From,TryFrom, orIntoIter callswhich uselessly convert to the same type.
Redundant code.
// format!() returns a `String`let s: String = format!("hello").into();Use instead:
let s: String = format!("hello");Checks for the use offormat!("string literal with no argument") andformat!("{}", foo) wherefoo is a string.
There is no point of doing that.format!("foo") canbe replaced by"foo".to_owned() if you really need aString. The evenworse&format!("foo") is often encountered in the wild.format!("{}", foo) can be replaced byfoo.clone() iffoo: String orfoo.to_owned()iffoo: &str.
let foo = "foo";format!("{}", foo);Use instead:
let foo = "foo";foo.to_owned();Checks for variable declarations immediately followed by aconditional affectation.
This is not idiomatic Rust.
let foo;if bar() { foo = 42;} else { foo = 0;}let mut baz = None;if bar() { baz = Some(42);}should be written
let foo = if bar() { 42} else { 0};let baz = if bar() { Some(42)} else { None};Checks forNonZero*::new_unchecked() being used in aconst context.
UsingNonZero*::new_unchecked() is anunsafe function and requires anunsafe context. When used in acontext evaluated at compilation time,NonZero*::new().unwrap() will provide the same result with identicalruntime performances while not requiringunsafe.
use std::num::NonZeroUsize;const PLAYERS: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(3) };Use instead:
use std::num::NonZeroUsize;const PLAYERS: NonZeroUsize = NonZeroUsize::new(3).unwrap();Checks for transmutes to the original type of the objectand transmutes that could be a cast.
Readability. The code tricks people into thinking thatsomething complex is going on.
core::intrinsics::transmute(t); // where the result type is the same as `t`'sChecks for usage ofvec![..] when using[..] wouldbe possible.
This is less efficient.
fn foo(_x: &[u8]) {}foo(&vec![1, 2]);Use instead:
foo(&[1, 2]);allow-useless-vec-in-tests: Whetheruseless_vec should ignore test functions or#[cfg(test)]
(default:false)
too-large-for-stack: The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
(default:200)
Checks for usage ofVec<Box<T>> where T: Sized anywhere in the code.Check theBox documentation for more information.
Vec already keeps its contents in a separate area onthe heap. So if youBox its contents, you just add another level of indirection.
struct X { values: Vec<Box<i32>>,}Better:
struct X { values: Vec<i32>,}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
vec-box-size-threshold: The size of the boxed type in bytes, where boxing in aVec is allowed
(default:4096)
Checks for calls topush immediately after creating a newVec.
If theVec is created usingwith_capacity this will only lint if the capacity is aconstant and the number of pushes is greater than or equal to the initial capacity.
If theVec is extended after the initial sequence of pushes and it was default initializedthen this will only lint after there were at least four pushes. This number may change inthe future.
Thevec![] macro is both more performant and easier to read thanmultiplepush calls.
let mut v = Vec::new();v.push(0);v.push(1);v.push(2);Use instead:
let v = vec![0, 1, 2];Finds occurrences ofVec::resize(0, an_int)
This is probably an argument inversion mistake.
vec![1, 2, 3, 4, 5].resize(0, 5)Use instead:
vec![1, 2, 3, 4, 5].clear()Checks for bit masks that can be replaced by a calltotrailing_zeros
x.trailing_zeros() >= 4 is much clearer thanx & 15 == 0
if x & 0b1111 == 0 { }Use instead:
if x.trailing_zeros() >= 4 { }verbose-bit-mask-threshold: The maximum allowed size of a bit mask before suggesting to use ‘trailing_zeros’
(default:1)
Checks for usage of File::read_to_end and File::read_to_string.
fs::{read, read_to_string} provide the same functionality whenbuf is empty with fewer imports and no intermediate values.See also:fs::read docs,fs::read_to_string docs
let mut f = File::open("foo.txt").unwrap();let mut bytes = Vec::new();f.read_to_end(&mut bytes).unwrap();Can be written more concisely as
let mut bytes = fs::read("foo.txt").unwrap();This lint warns when volatile load/store operations(write_volatile/read_volatile) are applied to composite types.
Volatile operations are typically used with memory mapped IO devices,where the precise number and ordering of load and store instructions isimportant because they can have side effects. This is well defined forprimitive types likeu32, but less well defined for structures andother composite types. In practice it’s implementation defined, and thebehavior can be rustc-version dependent.
As a result, code should only applywrite_volatile/read_volatile toprimitive types to be fully well-defined.
struct MyDevice { addr: usize, count: usize}fn start_device(device: *mut MyDevice, addr: usize, count: usize) { unsafe { device.write_volatile(MyDevice { addr, count }); }}Instead, operate on each primtive field individually:
struct MyDevice { addr: usize, count: usize}fn start_device(device: *mut MyDevice, addr: usize, count: usize) { unsafe { (&raw mut (*device).addr).write_volatile(addr); (&raw mut (*device).count).write_volatile(count); }}Checks for usage ofwaker.clone().wake()
Cloning the waker is not necessary,wake_by_ref() enables the same operationwithout extra cloning/dropping.
waker.clone().wake();Should be written
waker.wake_by_ref();Checks for while loops comparing floating point values.
If you increment floating point values, errors can compound,so, use integers instead if possible.
The lint will catch all while loops comparing floating pointvalues without regarding the increment.
let mut x = 0.0;while x < 42.0 { x += 1.0;}Use instead:
let mut x = 0;while x < 42 { x += 1;}Checks whether variables used within while loop conditioncan be (and are) mutated in the body.
If the condition is unchanged, entering the body of the loopwill lead to an infinite loop.
If thewhile-loop is in a closure, the check for mutation of thecondition variables in the body can cause false negatives. For example when onlyUpvara isin the condition and onlyUpvarb gets mutated in the body, the lint will not trigger.
let i = 0;while i > 10 { println!("let me loop forever!");}Detectsloop + match combinations that are easierwritten as awhile let loop.
Thewhile let loop is usually shorter and morereadable.
let y = Some(1);loop { let x = match y { Some(x) => x, None => break, }; // ..}Use instead:
let y = Some(1);while let Some(x) = y { // ..};Checks forwhile let expressions on iterators.
Readability. A simplefor loop is shorter and conveysthe intent better.
while let Some(val) = iter.next() { ..}Use instead:
for val in &mut iter { ..}Checks for wildcard dependencies in theCargo.toml.
As the edition guide says,it is highly unlikely that you work with any possible version of your dependency,and wildcard dependencies would cause unnecessary breakage in the ecosystem.
[dependencies]regex = "*"Use instead:
[dependencies]some_crate_1 = "~1.2.3"some_crate_2 = "=1.2.3"Checks for wildcard enum matches using_.
New enum variants added by library updates can be missed.
Suggested replacements may be incorrect if guards exhaustively cover somevariants, and also may not use correct path to enum if it’s not present in the current scope.
match x { Foo::A(_) => {}, _ => {},}Use instead:
match x { Foo::A(_) => {}, Foo::B(_) => {},}Checks for wildcard importsuse _::*.
wildcard imports can pollute the namespace. This is especially bad ifyou try to import something through a wildcard, that already has been imported by name froma different source:
use crate1::foo; // Imports a function named foouse crate2::*; // Has a function named foofoo(); // Calls crate1::fooThis can lead to confusing error messages at best and to unexpected behavior at worst.
Wildcard imports are allowed from modules that their name containsprelude. Many crates(including the standard library) provide modules named “prelude” specifically designedfor wildcard import.
Wildcard imports reexported throughpub use are also allowed.
use super::* is allowed in test modules. This is defined as any module with “test” in the name.
These exceptions can be disabled using thewarn-on-all-wildcard-imports configuration flag.
If macros are imported through the wildcard, this macro is not includedby the suggestion and has to be added by hand.
Applying the suggestion when explicit imports of the things imported with a glob importexist, may result inunused_imports warnings.
use crate1::*;foo();Use instead:
use crate1::foo;foo();allowed-wildcard-imports: List of path segments allowed to have wildcard imports.allowed-wildcard-imports = [ "utils", "common" ]warn_on_all_wildcard_imports = true.(default:[])
warn-on-all-wildcard-imports: Whether to emit warnings on all wildcard imports, including those fromprelude, fromsuper in tests,or forpub use reexports.
(default:false)
Checks for wildcard pattern used with others patterns in same match arm.
Wildcard pattern already covers any other pattern as it will match anyway.It makes the code less readable, especially to spot wildcard pattern use in match arm.
match s { "a" => {}, "bar" | _ => {},}Use instead:
match s { "a" => {}, _ => {},}This lint warns about the use of literals aswrite!/writeln! args.
Using literals aswriteln! args is inefficient(c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary(i.e., just put the literal in the format string)
writeln!(buf, "{}", "foo");Use instead:
writeln!(buf, "foo");This lint warns when you usewrite!() with a formatstring thatends in a newline.
You should usewriteln!() instead, which appends thenewline.
write!(buf, "Hello {}!\n", name);Use instead:
writeln!(buf, "Hello {}!", name);This lint warns when you usewriteln!(buf, "") toprint a newline.
You should usewriteln!(buf), which is simpler.
writeln!(buf, "");Use instead:
writeln!(buf);Nothing. This lint has been deprecated
clippy::wrong_self_convention now covers this case via theavoid-breaking-exported-api config.
Checks for methods with certain name prefixes or suffixes, and whichdo not adhere to standard conventions regarding howself is taken.The actual rules are:
| Prefix | Postfix | self taken | self type |
|---|---|---|---|
as_ | none | &self or&mut self | any |
from_ | none | none | any |
into_ | none | self | any |
is_ | none | &mut self or&self or none | any |
to_ | _mut | &mut self | any |
to_ | not_mut | self | Copy |
to_ | not_mut | &self | notCopy |
Note: Clippy doesn’t trigger methods withto_ prefix in:
Copy or not.&self is taken.The method signature is controlled by the trait and often&self is required for all types that implement the trait(see e.g. thestd::string::ToString trait).Clippy allowsPin<&Self> andPin<&mut Self> if&self and&mut self is required.
Please find more info here:https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
Consistency breeds readability. If you follow theconventions, your users won’t be surprised that they, e.g., need to supply amutable reference to aas_.. function.
impl X { fn as_str(self) -> &'static str { // .. }}Use instead:
impl X { fn as_str(&self) -> &'static str { // .. }}avoid-breaking-exported-api: Suppress lints whenever the suggested change would cause breakage for other crates.
(default:true)
Checks for transmutes that can’t ever be correct on anyarchitecture.
It’s basically guaranteed to be undefined behavior.
When accessing C, users might want to store pointersized objects inextradata arguments to save an allocation.
let ptr: *const T = core::intrinsics::transmute('x')Checks for0.0 / 0.0.
It’s less readable thanf32::NAN orf64::NAN.
let nan = 0.0f32 / 0.0;Use instead:
let nan = f32::NAN;Warns if an integral constant literal starts with0.
In some languages (including the infamous C languageand most of itsfamily), this marks an octal constant. In Rust however, this is a decimalconstant. This couldbe confusing for both the writer and a reader of the constant.
In Rust:
fn main() { let a = 0123; println!("{}", a);}prints123, while in C:
#include <stdio.h>int main() { int a = 0123; printf("%d\n", a);}prints83 (as83 == 0o123 while123 == 0o173).
Catch casts from0 to some pointer type
This generally meansnull and is better expressed as{std,core}::ptr::{null,null_mut}.
let a = 0 as *const u32;Use instead:
let a = std::ptr::null::<u32>();msrv: The minimum rust version that the project supports. Defaults to therust-version field inCargo.toml
(default:current version)
Checks for array or vec initializations which contain an expression with side effects,but which have a repeat count of zero.
Such an initialization, despite having a repeat length of 0, will still call the inner function.This may not be obvious and as such there may be unintended side effects in code.
fn side_effect() -> i32 { println!("side effect"); 10}let a = [side_effect(); 0];Use instead:
fn side_effect() -> i32 { println!("side effect"); 10}side_effect();let a: [i32; 0] = [];Checks for maps with zero-sized value types anywhere in the code.
Since there is only a single value for a zero-sized type, a mapcontaining zero sized values is effectively a set. Using a set in that case improvesreadability and communicates intent more clearly.
fn unique_words(text: &str) -> HashMap<&str, ()> { todo!();}Use instead:
fn unique_words(text: &str) -> HashSet<&str> { todo!();}Looks for code that spawns a process but never callswait() on the child.
As explained in thestandard library documentation,callingwait() is necessary on Unix platforms to properly release all OS resources associated with the process.Not doing so will effectively leak process IDs and/or other limited global resources,which can eventually lead to resource exhaustion, so it’s recommended to callwait() in long-running applications.Such processes are called “zombie processes”.
To reduce the rate of false positives, if the spawned process is assigned to a binding, the lint actually works the other way around; itconservatively checks that all uses of a variable definitely don’t callwait() and only then emits a warning.For that reason, a seemingly unrelated use can get called out as callingwait() in help messages.
If await() call exists in an if/then block but not in the else block (or there is no else block),then this still gets linted as not callingwait() in all code paths.Likewise, when early-returning from the function,wait() calls that appear after the return expressionare also not accepted.In other words, thewait() call must be unconditionally reachable after the spawn expression.
use std::process::Command;let _child = Command::new("ls").spawn().expect("failed to execute child");Use instead:
use std::process::Command;let mut child = Command::new("ls").spawn().expect("failed to execute child");child.wait().expect("failed to wait on child");Checks foroffset(_),wrapping_{add,sub}, etc. on raw pointers tozero-sized types
This is a no-op, and likely unintended
unsafe { (&() as *const ()).offset(1) };