Skip to content

Add a lint for forgetting futures#17054

Open
tdittr wants to merge 7 commits into
rust-lang:masterfrom
tdittr:mem_forget_impl_future
Open

Add a lint for forgetting futures#17054
tdittr wants to merge 7 commits into
rust-lang:masterfrom
tdittr:mem_forget_impl_future

Conversation

@tdittr
Copy link
Copy Markdown

@tdittr tdittr commented May 22, 2026

This adds a new lint to check when a Future is passed to mem::forget, thus not running any cancellation logic.

changelog: Add [mem_forget_future] lint

Open question: Should it be called forget_future instead? Similar to forget_non_drop.

@rustbot rustbot added the needs-fcp PRs that add, remove, or rename lints and need an FCP label May 22, 2026
@tdittr tdittr force-pushed the mem_forget_impl_future branch from 3c86ae2 to 1a5451f Compare May 22, 2026 15:22
@tdittr
Copy link
Copy Markdown
Author

tdittr commented May 22, 2026

r? @llogiq

@tdittr
Copy link
Copy Markdown
Author

tdittr commented May 22, 2026

CC: @datdenkikniet we talked about this at the embedded unconf

@tdittr tdittr force-pushed the mem_forget_impl_future branch 2 times, most recently from 2a31d5a to d0848b3 Compare May 22, 2026 15:55
@tdittr tdittr force-pushed the mem_forget_impl_future branch from d0848b3 to b9bd4f3 Compare May 22, 2026 15:58
@tdittr tdittr marked this pull request as ready for review May 23, 2026 09:10
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label May 23, 2026
Comment thread clippy_lints/src/drop_forget_ref.rs Outdated
Comment on lines +136 to +142
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}`"));
});
}
Copy link
Copy Markdown
Member

@samueltardieu samueltardieu May 23, 2026

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.

}

declare_clippy_lint! {
/// ### What it does
Copy link
Copy Markdown

@datdenkikniet datdenkikniet May 24, 2026

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

Copy link
Copy Markdown

@datdenkikniet datdenkikniet May 24, 2026

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

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

///
/// ### Why is this bad?
///
/// Futures use drop to be signalled that they were cancelled and should clean up their resources.
Copy link
Copy Markdown

@datdenkikniet datdenkikniet May 24, 2026

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

#[clippy::version = "1.97.0"]
pub FORGET_FUTURE,
suspicious,
"`mem::forget` usage on `Future` types, likely to cause problems with cancelation"
Copy link
Copy Markdown

@datdenkikniet datdenkikniet May 24, 2026

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-review Status: Awaiting review from the assignee but also interested parties

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants