Skip to content

Add unused_async_trait_impl lint#16244

Open
Wassasin wants to merge 25 commits into
rust-lang:masterfrom
Wassasin:unused_async_trait_impl
Open

Add unused_async_trait_impl lint#16244
Wassasin wants to merge 25 commits into
rust-lang:masterfrom
Wassasin:unused_async_trait_impl

Conversation

@Wassasin
Copy link
Copy Markdown

@Wassasin Wassasin commented Dec 15, 2025

View all comments

changelog: [unused_async_trait_impl]: new lint

Adds Lint that checks for trait impl functions that are unnecessarily async. By rewriting them to return a core::future::ready an unnecessary coroutine is prevented.

In an embedded context having many trivial async blocks potentially hurts code size significantly. This lint enables projects to quickly find the cases that this applies to.

@rustbot rustbot added the needs-fcp PRs that add, remove, or rename lints and need an FCP label Dec 15, 2025
@Wassasin Wassasin force-pushed the unused_async_trait_impl branch 2 times, most recently from 07c84ff to 29282db Compare December 15, 2025 15:56
@rustbot

This comment has been minimized.

@Wassasin Wassasin marked this pull request as ready for review January 5, 2026 10:16
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Jan 5, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Jan 5, 2026

r? @dswij

rustbot has assigned @dswij.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot

This comment has been minimized.

@rustbot rustbot added has-merge-commits PR has merge commits, merge with caution. S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Jan 5, 2026
@Wassasin Wassasin force-pushed the unused_async_trait_impl branch from 265ec66 to cf7dc77 Compare January 5, 2026 10:18
@rustbot rustbot removed has-merge-commits PR has merge commits, merge with caution. S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Jan 5, 2026
@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

@rustbot rustbot added has-merge-commits PR has merge commits, merge with caution. S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Jan 12, 2026
@Wassasin Wassasin force-pushed the unused_async_trait_impl branch from ac92856 to 2c59897 Compare January 12, 2026 09:38
@rustbot rustbot removed has-merge-commits PR has merge commits, merge with caution. S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Jan 12, 2026
Copy link
Copy Markdown
Contributor

@ada4a ada4a left a comment

Choose a reason for hiding this comment

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

I like the idea a lot! Left some suggestions

View changes since this review

Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Comment thread tests/ui/unused_async_trait_impl.rs Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Copy link
Copy Markdown
Contributor

@ada4a ada4a left a comment

Choose a reason for hiding this comment

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

Some more things, mostly concerning diagnostics

View changes since this review

Comment thread tests/ui/unused_async_trait_impl.stderr Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
@Wassasin Wassasin force-pushed the unused_async_trait_impl branch 2 times, most recently from 3a8be29 to c9ee761 Compare January 14, 2026 10:07
Copy link
Copy Markdown
Contributor

@ada4a ada4a left a comment

Choose a reason for hiding this comment

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

Awesome that you were able to get the indentation to work! There is just one thing I'm unsure about (and one smaller point)

View changes since this review

Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
Copy link
Copy Markdown
Contributor

@ada4a ada4a left a comment

Choose a reason for hiding this comment

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

Awesome work, I don't think I have anything else to add:)

Let's wait for an actual reviewer to look at this now

View changes since this review

@ada4a
Copy link
Copy Markdown
Contributor

ada4a commented Jan 14, 2026

Ah, one small thing regarding the PR description:

changelog: [unused_async_trait_impl]: added lint that checks for trait impl functions that are unnecessarily async. By rewriting them to return a core::future::ready an unnecessary coroutine is prevented.

This is a bit long of a changelog message 😅 You could leave it at just

changelog: [unused_async_trait_impl]: new lint

and put the actual description onto a separate paragraph.

Unsure about if this is the right method to replace the relevant code spans. Any feedback is very welcome.

I think this is resolved now? If so, feel free to remove (to reduce clutter in the eventual merge commit message)

@Wassasin
Copy link
Copy Markdown
Author

Awesome work, I don't think I have anything else to add:)

Let's wait for an actual reviewer to look at this now

View changes since this review

Thank you for your amazing help & feedback! I learned a lot about Clippy thanks to you!

@Wassasin Wassasin force-pushed the unused_async_trait_impl branch from cdff8eb to aa69ef5 Compare May 10, 2026 15:24
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented May 10, 2026

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@Wassasin
Copy link
Copy Markdown
Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels May 10, 2026
Copy link
Copy Markdown
Contributor

@ada4a ada4a left a comment

Choose a reason for hiding this comment

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

Just some wording nits:)

Let's start the FCP!

@rustbot label lint-nominated

View changes since this review

Comment thread clippy_lints/src/unused_async.rs Outdated
Comment thread clippy_lints/src/unused_async.rs Outdated
Comment thread clippy_lints/src/unused_async.rs Outdated
Comment on lines +277 to +281
// Fetch body snippet and truncate excess indentation. Like this:
// {
// 4
// }
let body_snippet = snippet_block_with_applicability(cx, body.value.span, "_", None, &mut app);
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.

One way to make this even nicer would be to strip away the curlies if the function body is just a single expression. I'd be insane to ask you to do that now, on top of everything you've already done, so could you please just leave a TODO comment for this?

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.

Changed it but the expansion is a bit messy. Initially I used the span of the expression, but we are losing any comments that might be been put in the body. I opted to remove the curly braces manually from the original body span.

Copy link
Copy Markdown
Author

@Wassasin Wassasin May 13, 2026

Choose a reason for hiding this comment

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

See this commit for the difference between expressions & manually trimming the braces

Comment thread clippy_lints/src/unused_async_trait_impl.rs Outdated
@rustbot rustbot added lint-nominated Create an FCP-thread on Zulip for this PR and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels May 12, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented May 12, 2026

This lint has been nominated for inclusion.

A FCP topic has been created on Zulip.

@rustbot rustbot added the S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) label May 12, 2026
Comment thread clippy_lints/src/unused_async.rs Outdated
Co-authored-by: Ada Alakbarova <58857108+ada4a@users.noreply.github.com>
@Wassasin Wassasin force-pushed the unused_async_trait_impl branch from c34bc19 to e1696b9 Compare May 13, 2026 07:24
Co-authored-by: Ada Alakbarova <58857108+ada4a@users.noreply.github.com>
@Wassasin Wassasin force-pushed the unused_async_trait_impl branch from b90a5ad to b56d50a Compare May 13, 2026 07:36
Comment thread clippy_lints/src/unused_async.rs Outdated
Comment thread clippy_lints/src/unused_async.rs Outdated
&& block.stmts.is_empty()
&& block.rules == BlockCheckMode::DefaultBlock
{
// Async body is just a simple expression: strip any curly braces.
Copy link
Copy Markdown
Contributor

@ada4a ada4a May 13, 2026

Choose a reason for hiding this comment

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

He just went ahead and did it, what a madlad.

Looking at the tests though, it looks like the initial version of this change made it so that the suggestion eats away any comments (and cfg-d out code, etc.), and the current version results in some rather awkward output as well..

I think a nice solution would be to drill down to the final expression (which is what's returned), and just wrap that in a std::future::ready call? One advantage of this is that it would work regardless of whether the function body contains any statements before the expression. And I don't think this would change the behavior of the function any more than the current fix does: if you wrap the whole body in an std::future::ready, its contents are still evaluated eagerly, right?

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.

... that is way better... in hindsight that's so obvious!

Copy link
Copy Markdown
Contributor

@ada4a ada4a left a comment

Choose a reason for hiding this comment

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

Some more, mostly mechanical, changes. Please apologize all the extra work -- I do think these particular changes are important to get in though..

View changes since this review

Comment on lines +261 to +268
&& let ExprKind::Closure(closure) = body.value.kind
&& let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind
&& let body = cx.tcx.hir_body(closure.body)
&& let ExprKind::Block(block, _) = body.value.kind
&& let Some(expr) = block.expr
&& let ExprKind::DropTemps(inner) = expr.kind
&& let ExprKind::Block(block, _) = inner.kind
&& let Some(tail_expr) = block.expr
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.

Async fn desugaring is somewhat unintuitive imo... What do you think about adding some documentation, like this perhaps:

Suggested change
&& let ExprKind::Closure(closure) = body.value.kind
&& let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind
&& let body = cx.tcx.hir_body(closure.body)
&& let ExprKind::Block(block, _) = body.value.kind
&& let Some(expr) = block.expr
&& let ExprKind::DropTemps(inner) = expr.kind
&& let ExprKind::Block(block, _) = inner.kind
&& let Some(tail_expr) = block.expr
// An async function like
// ```rs
// async fn get_random_number() -> i64 {
// do_something();
// 4
// }
// ```
// (roughly) desugars to
// ```rs
// fn get_random_number() -> impl Future<Output = i64> {
// async move {
// do_something();
// 4
// }
// }
// ```
//
// We first get to the `async move {}` block,
// which is the one and only expression in the body of the function..
&& let ExprKind::Closure(closure) = body.value.kind
&& let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind
&& let body = cx.tcx.hir_body(closure.body)
&& let ExprKind::Block(block, _) = body.value.kind
&& let Some(expr) = block.expr
&& let ExprKind::DropTemps(async_move_block) = expr.kind
// .. and then extract the trailing expression of the block (`4` in the
// example above), to wrap it in `std::future::ready`
&& let ExprKind::Block(block, _) = async_move_block.kind
&& let Some(tail_expr) = block.expr

Comment on lines +281 to +282
let signature_snippet = snippet_with_applicability(cx, sig.decl.output.span(), "_", &mut app);
let tail_snippet = snippet_with_applicability(cx, tail_expr.span, "_", &mut app).to_string();
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.

Just in theory, both of these might be macro calls.

For the type, this could look like this: (I concur that this is somewhat contrived)

macro_rules! opt {
    ($t:ty) => { Option<$t> }
}

async fn foo() -> opt!(u32) {
    Some(4)
}

And for the trailing expression:

async fn foo() -> Vec<u32> {
    vec![]
}

Thankfully, the fix is pretty simple:

Suggested change
let signature_snippet = snippet_with_applicability(cx, sig.decl.output.span(), "_", &mut app);
let tail_snippet = snippet_with_applicability(cx, tail_expr.span, "_", &mut app).to_string();
let ctxt = impl_item.span.ctxt();
let signature_snippet = snippet_with_context(cx, sig.decl.output.span(), "_", ctxt, &mut app);
let tail_snippet = snippet_with_context(cx, tail_expr.span, "_", ctxt, &mut app).to_string();

(See the documentation on snippet_with_context and the contents of #16809 for more information)

Could you please add the examples above as test cases?

format!("{builtin_crate}::future::ready({tail_snippet})"),
),
];
diag.help(format!(
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 this should be a note, as it doesn't by itself say what to do, but rather gives some context around the fix. See https://doc.rust-lang.org/clippy/development/emitting_lints.html#how-to-choose-between-notes-help-messages-and-suggestions

Suggested change
diag.help(format!(
diag.note(format!(

Comment on lines +299 to +302
format!("consider removing the `async` from this function and returning `impl Future<Output = {signature_snippet}>` instead"),
sugg,
app
);
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.

nit: it seems like rustfmt gave up on formatting this part because of a format literal that's too long. Breaking it up should fix this:

Suggested change
format!("consider removing the `async` from this function and returning `impl Future<Output = {signature_snippet}>` instead"),
sugg,
app
);
format!("consider removing the `async` from this function \
and returning `impl Future<Output = {signature_snippet}>` instead"),
sugg,
app
);

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

Labels

lint-nominated Create an FCP-thread on Zulip for this PR needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants