Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6747,6 +6747,7 @@ Released 2018-09-13
[`for_loop_over_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_result
[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
[`forget_future`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_future
[`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
[`format_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_collect
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::doc::UNNECESSARY_SAFETY_DOC_INFO,
crate::double_parens::DOUBLE_PARENS_INFO,
crate::drop_forget_ref::DROP_NON_DROP_INFO,
crate::drop_forget_ref::FORGET_FUTURE_INFO,
crate::drop_forget_ref::FORGET_NON_DROP_INFO,
crate::drop_forget_ref::MEM_FORGET_INFO,
crate::duplicate_mod::DUPLICATE_MOD_INFO,
Expand Down
41 changes: 39 additions & 2 deletions clippy_lints/src/drop_forget_ref.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_must_use_func_call;
use clippy_utils::res::MaybeDef;
use clippy_utils::ty::{is_copy, is_must_use_ty};
use clippy_utils::ty::{implements_trait, is_copy, is_must_use_ty};
use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
Expand All @@ -28,6 +28,29 @@ declare_clippy_lint! {
"call to `std::mem::drop` with a value which does not implement `Drop`"
}

declare_clippy_lint! {
/// ### What it does

@datdenkikniet datdenkikniet May 24, 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.

Does this also catch other non-drop cases (i.e. ManuallyDrop<impl Future>)? If not, we should be more specific and say that "Checks whether std::mem::forget is called on a Future that implements Drop" or something like that..

View changes since the review

@datdenkikniet datdenkikniet May 24, 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.

I've double-checked, and the following does indeed not generate the lint (which is fine, but supports my request for making this description more specific):

    #[expect(clippy::forget_non_drop)]
    let thing = std::mem::ManuallyDrop::new(async {});

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.

It's fine in the sense that we can always add ManuallyDrop<impl Future> detection later, but not half-implying that this lint already does it would be good :D

///
/// Checks for not executing `Drop` on a `Future`.
///
/// ### Why is this bad?
///
/// Futures use drop to be signalled that they were cancelled and should clean up their resources.

@datdenkikniet datdenkikniet May 24, 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.

I think we can be clearer here. The statement is not technically wrong, but it's arguing from the viewpoint of "we want to drop all Futures", as opposed to "we do not want to forget a Future".

Additionally, Drop doesn't necessarily signal cancellation: if a Future is Poll::Complete, there is likely "no" cancellation necessary.

I propose something like:

"When dropped, Futures may execute important cleanup logic, e.g. to signal that they have been cancelled to an executor/context/hardware/what-have-you if they have not completed. forget-ing a Future prevents this cleanup from occurring".

View changes since the review

///
/// ### Example
/// ```no_run
/// std::mem::forget(async {});
/// ```
/// Use instead:
/// ```no_run
/// std::mem::drop(async {});
/// ```
#[clippy::version = "1.97.0"]
pub FORGET_FUTURE,
suspicious,
"`mem::forget` usage on `Future` types, likely to cause problems with cancelation"

@datdenkikniet datdenkikniet May 24, 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.

Another "can we be more specific" request: "mem::forget on Future types prevents potential cleanup from being performed" or something like that

View changes since the review

}

declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `std::mem::forget` with a value that does not implement `Drop`.
Expand Down Expand Up @@ -70,12 +93,18 @@ declare_clippy_lint! {
"`mem::forget` usage on `Drop` types, likely to cause memory leaks"
}

declare_lint_pass!(DropForgetRef => [DROP_NON_DROP, FORGET_NON_DROP, MEM_FORGET]);
declare_lint_pass!(DropForgetRef => [
DROP_NON_DROP,
FORGET_FUTURE,
FORGET_NON_DROP,
MEM_FORGET,
]);

const DROP_NON_DROP_SUMMARY: &str = "call to `std::mem::drop` with a value that does not implement `Drop`. \
Dropping such a type only extends its contained lifetimes";
const FORGET_NON_DROP_SUMMARY: &str = "call to `std::mem::forget` with a value that does not implement `Drop`. \
Forgetting such a type is the same as dropping it";
const FORGET_FUTURE_SUMMARY: &str = "forgetting a Future might cause problems with cancelation";

impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
Expand Down Expand Up @@ -104,6 +133,14 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
(DROP_NON_DROP, DROP_NON_DROP_SUMMARY.into(), Some(arg.span))
},
sym::mem_forget => {
if let Some(future_trait) = cx.tcx.lang_items().future_trait()
&& implements_trait(cx, arg_ty, future_trait, &[])
{
span_lint_and_then(cx, FORGET_FUTURE, expr.span, FORGET_FUTURE_SUMMARY, |diag| {
diag.span_note(arg.span, format!("argument has type `{arg_ty}`"));
});
}

@samueltardieu samueltardieu May 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Aren't you also linting Future types even if they don't implement Drop? What would be the problem there?

View changes since the review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Oh yes that is a very good point. I'll change that

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Weirdly the future created by an async block or async fn always implements Drop? No matter if it captures anything that implements Drop.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Weirdly the future created by an async block or async fn always implements Drop? No matter if it captures anything that implements Drop.

I'd expect that, at least if any object inside the async block implements Drop and can be alive around any .await. You should try and check if this is easy to determine.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It looks like the drop glue is always generated, but no explicit Drop impl is around. So for example this will fail to compile:

async fn foo() {
    let x = Box::new(());
    core::future::ready(()).await;
    let _ = x;
}

fn bar() -> impl Future<Output = ()> + Drop {
    foo()
}

Are you by any chance also at RustWeek at the moment?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are you by any chance also at RustWeek at the moment?

Yes, but leaving right after lunch.


if arg_ty.needs_drop(cx.tcx, cx.typing_env()) {
(
MEM_FORGET,
Expand Down
34 changes: 34 additions & 0 deletions tests/ui/forget_future.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![warn(clippy::forget_future)]

use core::mem;
use std::pin::Pin;
use std::task::{Context, Poll};

fn main() {
let fut = foo();
mem::forget(fut);
//~^ forget_future

mem::forget(async {});
//~^ forget_future

let fut = MyFuture;
mem::forget(fut);
//~^ forget_future
}

async fn foo() {}

struct MyFuture;

impl Future for MyFuture {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

impl Drop for MyFuture {
fn drop(&mut self) {}
}
40 changes: 40 additions & 0 deletions tests/ui/forget_future.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
error: forgetting a Future might cause problems with cancelation
--> tests/ui/forget_future.rs:9:5
|
LL | mem::forget(fut);
| ^^^^^^^^^^^^^^^^
|
note: argument has type `impl std::future::Future<Output = ()>`
--> tests/ui/forget_future.rs:9:17
|
LL | mem::forget(fut);
| ^^^
= note: `-D clippy::forget-future` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::forget_future)]`

error: forgetting a Future might cause problems with cancelation
--> tests/ui/forget_future.rs:12:5
|
LL | mem::forget(async {});
| ^^^^^^^^^^^^^^^^^^^^^
|
note: argument has type `{async block@tests/ui/forget_future.rs:12:17: 12:22}`
--> tests/ui/forget_future.rs:12:17
|
LL | mem::forget(async {});
| ^^^^^^^^

error: forgetting a Future might cause problems with cancelation
--> tests/ui/forget_future.rs:16:5
|
LL | mem::forget(fut);
| ^^^^^^^^^^^^^^^^
|
note: argument has type `MyFuture`
--> tests/ui/forget_future.rs:16:17
|
LL | mem::forget(fut);
| ^^^

error: aborting due to 3 previous errors

6 changes: 5 additions & 1 deletion tests/ui/mem_forget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::mem as memstuff;
use std::mem::forget as forgetSomething;

#[warn(clippy::mem_forget)]
#[allow(forgetting_copy_types)]
#[allow(forgetting_copy_types, clippy::forget_future)]
fn main() {
let five: i32 = 5;
forgetSomething(five);
Expand All @@ -26,5 +26,9 @@ fn main() {
std::mem::forget(string);
//~^ mem_forget

let fut = async {};
std::mem::forget(fut);
//~^ mem_forget

std::mem::forget(7);
}
10 changes: 9 additions & 1 deletion tests/ui/mem_forget.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,13 @@ LL | std::mem::forget(string);
|
= note: argument has type `std::string::String`

error: aborting due to 4 previous errors
error: usage of `mem::forget` on type with `Drop` fields
--> tests/ui/mem_forget.rs:30:5
|
LL | std::mem::forget(fut);
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: argument has type `{async block@tests/ui/mem_forget.rs:29:15: 29:20}`

error: aborting due to 5 previous errors