Add a lint for forgetting futures#17054
Conversation
3c86ae2 to
1a5451f
Compare
|
r? @llogiq |
|
CC: @datdenkikniet we talked about this at the embedded unconf |
2a31d5a to
d0848b3
Compare
d0848b3 to
b9bd4f3
Compare
| 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}`")); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Aren't you also linting Future types even if they don't implement Drop? What would be the problem there?
There was a problem hiding this comment.
Oh yes that is a very good point. I'll change that
There was a problem hiding this comment.
Weirdly the future created by an async block or async fn always implements Drop? No matter if it captures anything that implements Drop.
There was a problem hiding this comment.
Weirdly the future created by an
asyncblock orasync fnalways implementsDrop? No matter if it captures anything that implementsDrop.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Are you by any chance also at RustWeek at the moment?
Yes, but leaving right after lunch.
| } | ||
|
|
||
| declare_clippy_lint! { | ||
| /// ### What it does |
There was a problem hiding this comment.
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..
There was a problem hiding this comment.
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 {});There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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".
| #[clippy::version = "1.97.0"] | ||
| pub FORGET_FUTURE, | ||
| suspicious, | ||
| "`mem::forget` usage on `Future` types, likely to cause problems with cancelation" |
There was a problem hiding this comment.
Another "can we be more specific" request: "mem::forget on Future types prevents potential cleanup from being performed" or something like that
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] lintOpen question: Should it be called
forget_futureinstead? Similar toforget_non_drop.