Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions compiler/rustc_lint/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,13 @@ pub(crate) enum NonBindingLet {
#[subdiagnostic]
sub: NonBindingLetSub,
},
#[diag("non-binding let on a `DropGuard`")]
DropGuard {
#[label("this `DropGuard` is not assigned to a binding and is immediately dropped")]
pat: Span,
#[subdiagnostic]
sub: NonBindingLetSub,
},
#[diag("non-binding let on a type that has a destructor")]
DropType {
#[subdiagnostic]
Expand Down
48 changes: 47 additions & 1 deletion compiler/rustc_lint/src/let_underscore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,45 @@ declare_lint! {
"non-binding let on a synchronization lock"
}

declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK]);
declare_lint! {
/// The `let_underscore_drop_guard` lint checks for statements which don't bind
/// a `DropGuard` to anything, causing its closure to run immediately instead
/// of at end of scope, which is typically incorrect.
///
/// ### Example
#[cfg_attr(bootstrap, doc = "```rust")]
#[cfg_attr(not(bootstrap), doc = "```rust,compile_fail")]
/// use std::mem::DropGuard;
///
/// // The closure runs immediately instead of at the end of the scope,
/// // which is probably not intended.
/// let _ = DropGuard::new((), |_| {
/// println!("cleaning up");
/// });
#[cfg_attr(bootstrap, doc = "```")]
#[cfg_attr(not(bootstrap), doc = "```")]
///
/// {{produces}}
///
/// ### Explanation
///
/// Statements which assign an expression to an underscore causes the
/// expression to immediately drop instead of extending the expression's
/// lifetime to the end of the scope. This is usually unintended,
/// especially for types like `DropGuard`, which are typically used to
/// ensure running some cleanup code at the end of a scope.
///
/// If you want to extend the expression's lifetime to the end of the scope,
/// assign an underscore-prefixed name (such as `_foo`) to the expression.
/// If you do actually want to drop the expression immediately, then
/// calling `std::mem::drop` on the expression is clearer and helps convey
/// intent.
pub LET_UNDERSCORE_DROP_GUARD,

@traviscross traviscross Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub LET_UNDERSCORE_DROP_GUARD,
pub LET_UNDERSCORE_DROP_GUARDS,

Given our RFC 0344 guidance, this should be named in the plural. (I.e., it reads more naturally as "allow let underscore drop guards".)

Though, here, annoyingly, that puts it at odds with let_underscore_lock. Still, I'd err on the side of following our convention and treating the other one as a miss.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this already and I disagree. Adding an s there implies that there are multiple drop guards. The thing that's logically "plural" is "let statements", but I don't think we should needlessly add the word "statements" either.

@traviscross traviscross Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Here's how I'd frame what I understand to be your reading and, conversely, how I read it.

You're, I think, reading this as a transcription of the statement form: let _: DropGuard. In this framing, the part on the right names a token-like part of one construct rather than the class. We see this also in while_true, dyn_drop, unused_unsafe, break_with_label_and_loop, etc. So there's good precedent for it. And it's correct that those should not be pluralized.

The way I was reading it is as follows: "drop guard" is the head noun (i.e., the main thing) and "let underscore" is the attributive modifier (i.e., the adjective, essentially). (In unused_variables, "variables" is the head noun and "unused" is the attributive modifier.) So I read "drop guards (in let underscore position)" as the thing that we're allowing, which leads to my sense of why, in this reading, it should be plural, since it does name the class.

But I buy your reading here too, so I'll check a box.

Deny,
"non-binding let on a `DropGuard`"
}

declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_DROP_GUARD]);

const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
rustc_span::sym::MutexGuard,
Expand Down Expand Up @@ -144,6 +182,7 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
.any(|guard_symbol| cx.tcx.is_diagnostic_item(*guard_symbol, adt.did())),
_ => false,
};
let is_drop_guard = matches!(ty.kind(), ty::Adt(adt, _) if cx.tcx.is_diagnostic_item(sym::DropGuard, adt.did()));

let can_use_init = is_top_level.then_some(local.init).flatten();

Expand All @@ -161,6 +200,13 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
span,
NonBindingLet::SyncLock { sub, pat: pat.span },
);
} else if is_drop_guard {
let span = MultiSpan::from_span(pat.span);
cx.emit_span_lint(
LET_UNDERSCORE_DROP_GUARD,
span,
NonBindingLet::DropGuard { sub, pat: pat.span },
);
// Only emit let_underscore_drop for top-level `_` patterns.
} else if can_use_init.is_some() {
cx.emit_span_lint(LET_UNDERSCORE_DROP, local.span, NonBindingLet::DropType { sub });
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ symbols! {
Deref,
DispatchFromDyn,
Display,
DropGuard,
Dual,
DualOnly,
Dualv,
Expand Down
1 change: 1 addition & 0 deletions library/core/src/mem/drop_guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::ops::{Deref, DerefMut};
#[stable(feature = "drop_guard", since = "1.100.0")]
#[doc(alias = "ScopeGuard")]
#[doc(alias = "defer")]
#[rustc_diagnostic_item = "DropGuard"]
pub struct DropGuard<#[rustc_assert_variance(covariant)] T, #[rustc_assert_variance(covariant)] F>
where
F: FnOnce(T),
Expand Down
23 changes: 23 additions & 0 deletions tests/ui/lint/let_underscore/let_underscore_drop_guard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use std::mem::DropGuard;

struct Struct<T> {
a: T,
}

fn main() {
let _ = DropGuard::new((), |_| {});
//~^ERROR non-binding let on a `DropGuard`
_ = DropGuard::new((), |_| {});
//~^ERROR non-binding let on a `DropGuard`

let (_, _) = (DropGuard::new((), |_| {}), 1);
//~^ERROR non-binding let on a `DropGuard`
(_, _) = (DropGuard::new((), |_| {}), 1);
//~^ERROR non-binding let on a `DropGuard`

let (_a, Struct { a: _ }) = (0, Struct { a: DropGuard::new((), |_| {}) });
//~^ERROR non-binding let on a `DropGuard`
let _b;
(_b, Struct { a: _ }) = (0, Struct { a: DropGuard::new((), |_| {}) });
//~^ERROR non-binding let on a `DropGuard`
}
86 changes: 86 additions & 0 deletions tests/ui/lint/let_underscore/let_underscore_drop_guard.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
error: non-binding let on a `DropGuard`
--> $DIR/let_underscore_drop_guard.rs:8:9
|
LL | let _ = DropGuard::new((), |_| {});
| ^ this `DropGuard` is not assigned to a binding and is immediately dropped
|
= note: `#[deny(let_underscore_drop_guard)]` on by default
help: consider binding to an unused variable to avoid immediately dropping the value
|
LL | let _unused = DropGuard::new((), |_| {});
| ++++++
help: consider immediately dropping the value
|
LL - let _ = DropGuard::new((), |_| {});
LL + drop(DropGuard::new((), |_| {}));
|

error: non-binding let on a `DropGuard`
--> $DIR/let_underscore_drop_guard.rs:10:5
|
LL | _ = DropGuard::new((), |_| {});
| ^ this `DropGuard` is not assigned to a binding and is immediately dropped
|
help: consider binding to an unused variable to avoid immediately dropping the value
|
LL - _ = DropGuard::new((), |_| {});
LL + let _unused = DropGuard::new((), |_| {});
|
help: consider immediately dropping the value
|
LL - _ = DropGuard::new((), |_| {});
LL + drop(DropGuard::new((), |_| {}));
|

error: non-binding let on a `DropGuard`
--> $DIR/let_underscore_drop_guard.rs:13:10
|
LL | let (_, _) = (DropGuard::new((), |_| {}), 1);
| ^ this `DropGuard` is not assigned to a binding and is immediately dropped
|
= help: consider immediately dropping the value using `drop(..)` after the `let` statement
help: consider binding to an unused variable to avoid immediately dropping the value
|
LL | let (_unused, _) = (DropGuard::new((), |_| {}), 1);
| ++++++

error: non-binding let on a `DropGuard`
--> $DIR/let_underscore_drop_guard.rs:15:6
|
LL | (_, _) = (DropGuard::new((), |_| {}), 1);
| ^ this `DropGuard` is not assigned to a binding and is immediately dropped
|
help: consider binding to an unused variable to avoid immediately dropping the value
--> $DIR/let_underscore_drop_guard.rs:15:6
|
LL | (_, _) = (DropGuard::new((), |_| {}), 1);
| ^
= help: consider immediately dropping the value using `drop(..)` after the `let` statement

error: non-binding let on a `DropGuard`
--> $DIR/let_underscore_drop_guard.rs:18:26
|
LL | let (_a, Struct { a: _ }) = (0, Struct { a: DropGuard::new((), |_| {}) });
| ^ this `DropGuard` is not assigned to a binding and is immediately dropped
|
= help: consider immediately dropping the value using `drop(..)` after the `let` statement
help: consider binding to an unused variable to avoid immediately dropping the value
|
LL | let (_a, Struct { a: _unused }) = (0, Struct { a: DropGuard::new((), |_| {}) });
| ++++++

error: non-binding let on a `DropGuard`
--> $DIR/let_underscore_drop_guard.rs:21:22
|
LL | (_b, Struct { a: _ }) = (0, Struct { a: DropGuard::new((), |_| {}) });
| ^ this `DropGuard` is not assigned to a binding and is immediately dropped
|
help: consider binding to an unused variable to avoid immediately dropping the value
--> $DIR/let_underscore_drop_guard.rs:21:22
|
LL | (_b, Struct { a: _ }) = (0, Struct { a: DropGuard::new((), |_| {}) });
| ^
= help: consider immediately dropping the value using `drop(..)` after the `let` statement

error: aborting due to 6 previous errors

Loading