Skip to content

Commit 821b32b

Browse files
committed
Add let_underscore_drop lint.
This lint checks for statements similar to `let _ = foo`, where `foo` is a type that implements `Drop`. These types of let statements cause the expression in them to be dropped immediately, instead of at the end of the scope. Such behavior can be surprizing, especially if you are relying on the value to be dropped at the end of the scope. Instead, the binding should be an underscore prefixed name (like `_unused`) or the value should explicitly be passed to `std::mem::drop()` if the value really should be dropped immediately.
1 parent e57884b commit 821b32b

File tree

4 files changed

+96
-0
lines changed

4 files changed

+96
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use crate::{LateContext, LateLintPass, LintContext};
2+
use rustc_hir as hir;
3+
4+
declare_lint! {
5+
/// The `let_underscore_drop` lint checks for statements which don't bind
6+
/// an expression which has a non-trivial Drop implementation to anything,
7+
/// causing the expression to be dropped immediately instead of at end of
8+
/// scope.
9+
///
10+
/// ### Example
11+
/// ```rust
12+
/// struct SomeStruct;
13+
/// impl Drop for SomeStruct {
14+
/// fn drop(&mut self) {
15+
/// println!("Dropping SomeStruct");
16+
/// }
17+
/// }
18+
///
19+
/// fn main() {
20+
/// // SomeStuct is dropped immediately instead of at end of scope,
21+
/// // so "Dropping SomeStruct" is printed before "end of main".
22+
/// // The order of prints would be reversed if SomeStruct was bound to
23+
/// // a name (such as "_foo").
24+
/// let _ = SomeStruct;
25+
/// println!("end of main");
26+
/// }
27+
/// ```
28+
/// ### Explanation
29+
///
30+
/// Statements which assign an expression to an underscore causes the
31+
/// expression to immediately drop instead of extending the expression's
32+
/// lifetime to the end of the scope. This is usually unintended,
33+
/// especially for types like `MutexGuard`, which are typically used to
34+
/// lock a mutex for the duration of an entire scope.
35+
///
36+
/// If you want to extend the expression's lifetime to the end of the scope,
37+
/// assign an underscore-prefixed name (such as `_foo`) to the expression.
38+
/// If you do actually want to drop the expression immediately, then
39+
/// calling `std::mem::drop` on the expression is clearer and helps convey
40+
/// intent.
41+
pub LET_UNDERSCORE_DROP,
42+
Warn,
43+
"non-binding let on a type that implements `Drop`"
44+
}
45+
46+
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP]);
47+
48+
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
49+
fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
50+
if !matches!(local.pat.kind, hir::PatKind::Wild) {
51+
return;
52+
}
53+
if let Some(init) = local.init {
54+
let init_ty = cx.typeck_results().expr_ty(init);
55+
let needs_drop = init_ty.needs_drop(cx.tcx, cx.param_env);
56+
if needs_drop {
57+
cx.struct_span_lint(LET_UNDERSCORE_DROP, local.span, |lint| {
58+
lint.build("non-binding let on a type that implements `Drop`")
59+
.help("consider binding to an unused variable")
60+
.help("consider explicitly droping with `std::mem::drop`")
61+
.emit();
62+
})
63+
}
64+
}
65+
}
66+
}

compiler/rustc_lint/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ mod expect;
5454
pub mod hidden_unicode_codepoints;
5555
mod internal;
5656
mod late;
57+
mod let_underscore;
5758
mod levels;
5859
mod methods;
5960
mod non_ascii_idents;
@@ -85,6 +86,7 @@ use builtin::*;
8586
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
8687
use hidden_unicode_codepoints::*;
8788
use internal::*;
89+
use let_underscore::*;
8890
use methods::*;
8991
use non_ascii_idents::*;
9092
use non_fmt_panic::NonPanicFmt;
@@ -199,6 +201,7 @@ macro_rules! late_lint_mod_passes {
199201
VariantSizeDifferences: VariantSizeDifferences,
200202
BoxPointers: BoxPointers,
201203
PathStatements: PathStatements,
204+
LetUnderscore: LetUnderscore,
202205
// Depends on referenced function signatures in expressions
203206
UnusedResults: UnusedResults,
204207
NonUpperCaseGlobals: NonUpperCaseGlobals,
@@ -314,6 +317,8 @@ fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
314317
REDUNDANT_SEMICOLONS
315318
);
316319

320+
add_lint_group!("let_underscore", LET_UNDERSCORE_DROP);
321+
317322
add_lint_group!(
318323
"rust_2018_idioms",
319324
BARE_TRAIT_OBJECTS,

src/test/ui/let_underscore_drop.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// run-pass
2+
3+
struct NontrivialDrop;
4+
5+
impl Drop for NontrivialDrop {
6+
fn drop(&mut self) {
7+
println!("Dropping!");
8+
}
9+
}
10+
11+
fn main() {
12+
let _ = NontrivialDrop; //~WARNING non-binding let on a type that implements `Drop`
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
warning: non-binding let on a type that implements `Drop`
2+
--> $DIR/let_underscore_drop.rs:12:5
3+
|
4+
LL | let _ = NontrivialDrop;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(let_underscore_drop)]` on by default
8+
= help: consider binding to an unused variable
9+
= help: consider explicitly droping with `std::mem::drop`
10+
11+
warning: 1 warning emitted
12+

0 commit comments

Comments
 (0)