-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
unused_async doesn't trigger if the function is passed into another function #13466
Comments
This is the reason for the false negative (or the intention behind not linting). It doesn't emit a warning here because it thinks that the asyncness might be needed to prevent false positives, but the check is rather conservative and doesn't actually check much (it checks if the async function is used anywhere except as a callee). But I think we can special case async fns passed as an argument if the function really doesn't require it |
Oh neat, thanks for the clarification! Unfortunately, my actual code is more affected by the inconsistency with the traits. It's something like this: /// Trait from an external library
trait FutureFn {}
impl<F: FnOnce() -> Fut, Fut: Future<Output = ()>> FutureFn for F {}
fn takes_future_wrapper<F>(fut_wrapped: FutureWrapper<F>)
where
F: FutureFn,
{
}
struct FutureWrapper<F>(F);
impl From<F> for FutureWrapper<F> { ... }
async fn unused_async() {}
fn main() {
takes_future_wrapper(unused_async.into());
} ...which seems difficult to detect through all those layers of indirection. For my particular use case, the current conservative behavior would work if it also applied to method calls on the async function. |
What's the false negative in this example code? It is emitting a warning here, and if anything this looks like a false positive, seeing the To be clear, the current conservative behavior is, "does the function have no await statements and is never referenced anywhere except as a callee or a method call receiver" (phrased that wrongly in my last comment) |
You're exactly right, it's a false positive in that case - sorry I was unclear. To clarify - in my codebase I have some instances where Here's a playground link with my example cleaned up to show the false positive: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1e26f5e86e19e8cd4c97b68dc15d718f |
Btw @y21, I also toyed a bit with this lint, and it looks like the match against |
Remove method call receiver special casing in `unused_async` lint Fixes the false positive mentioned in #13466 (comment). The false negative in the OP would be nice to fix too, but I'd rather do that in a separate PR because it's much more involved Before this change, the `unused_async` lint would check if the async fn is also used anywhere and avoid linting if so. The exception is if the async function is immediately called, because the returned future handling can be easily removed (and also if we don't have some exceptions then the lint wouldn't trigger anywhere) *or* if it's a method call receiver. I'm not exactly sure why I implemented that special casing for method call receivers in #11200, but it doesn't make much sense in hindsight imo. Especially given that method calls are essentially equivalent to function calls with the receiver as the first argument, which was the primary motivation for not linting in the first place (async fn passed to another function, like `axum::get(handler)` where handler has to be an async fn). changelog: none
The false positive above was fixed in #13471: the only case where the lint now warns is if you always directly call the async function (not if it appears in a method call receiver like before). I also wrote up a fix for both false negatives in the original issue description where it will emit a warning if you pass the async function to a function if the where clauses on the function allow it, and propagating it up the expression chain so it can validate multiple layers of calls, e.g. However, that is a lot of code and complicates the lint a fair bit, and I'm questioning whether that's actually useful to special case and would really be hit by anyone in real code. I can't think of a practical use case for a function that can take an async function as well as non-async ones. Or rather, I don't know if you can usefully be generic over "might or might not be async" today Do you think that emitting a warning for your original reproducer would be useful/reach real code, or is this just a very constructed case? What can you actually do with the |
I don't think so. The closest thing to a use case for functions that can take a sync or async function that I can think of is let stream: impl Stream<Output = T> = ...;
let collection = stream.map(do_something_sync).collect::<Vec<_>>().await;
// OR
let collection = stream.map(do_something_async).collect::<FuturesUnordered<_>>().collect::<Vec<_>>().await; So let collection = stream.map(do_something_async).collect::<Vec<_>>().await; But if you did anything with Anything I can think of for functions that receive both sync and async has to do with passing the result on somewhere else, so you'd either 1. fail to compile, so the compiler'd already tell you something's wrong or 2. detect that it was needed further up the call chain with your complex logic, so it wouldn't lint regardless. Just grasping at straws, but it would be useful if it would lint on the following, but it doesn't seem possible to detect something like this generically so I think out of scope for this lint. This: async fn do_something(t: T) -> U {}
let collection = stream.then(do_something).collect::<Vec<_>>().await; would be replaced with this: fn do_something(t: T) -> U {}
let collection = stream.map(do_something).collect::<Vec<_>>().await; So overall no it doesn't seem all that useful, except that it's a little confusing the way it works currently. Would putting a note on this page about that behavior make sense? That's where I went to try to figure out what it was doing. |
Summary
If an async function (with no await statements) is passed into another function as an argument, it no longer triggers the
unused_async
lint.Believe this is a relatively recent issue - came up now because I replaced our allows with expects, but we've had a good amount of these unused_asyncs for a while.
Side note - at first I thought this was because the function it was passed into required its argument to be an async function and
unused_async
was smart enough to detect that, but unfortunately not. That'd be a nice feature though!Lint Name
unused_async
Reproducer
I tried this code:
I expected to see this happen:
unused_async
triggers as there are noawait
statements.Instead, this happened:
unused_async
does not trigger, andexpect
produces "unfulfilled lint".Interestingly, this only happens when my async fn is passed into another function, not if you call a method on the async fn (even if it's the same function):
playground
Version
rustc 1.81.0 (eeb90cda1 2024-09-04)
binary: rustc
commit-hash: eeb90cda1969383f56a2637cbd3037bdf598841c
commit-date: 2024-09-04
host: x86_64-unknown-linux-gnu
release: 1.81.0
LLVM version: 18.1.7
The text was updated successfully, but these errors were encountered: