Skip to content
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

Closure in async block fails to compile when it should, issues incorrect/misleading diagnostic #96128

Open
cdhowie opened this issue Apr 16, 2022 · 3 comments
Labels
A-async-await Area: Async & Await AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug.

Comments

@cdhowie
Copy link

cdhowie commented Apr 16, 2022

I've put the code reproducing this issue in the cdhowie/warp-lifetime-issue-repro repository as it has a dependency on warp. I have not been able to build a reproduction of this issue outside of warp or rocket-rs, but I believe the issue is with the compiler nonetheless, for a few reasons. I have been able to reproduce the issue on all of the following Rust Docker images:

  • docker.io/library/rust:1.59-bullseye (d6c4db7b2530)
  • docker.io/library/rust:1.60-bullseye (5593c6ce4c4e)
  • docker.io/rustlang/rust:nightly-bullseye (cf477c958fa3 -- 1.62.0-nightly (e7575f9 2022-04-14))

The summary is that passing a very simple non-capturing closure in an async route handler causes a lifetime mismatch error, referencing a type that is not used or demanded in the handler:

error[E0308]: mismatched types
   --> src/main.rs:39:6
    |
39  |     .and_then(|_id, provider: Arc<T>| async move {
    |      ^^^^^^^^ lifetime mismatch
    |
    = note: expected type `for<'r> FnOnce<(&&Item,)>`
               found type `for<'r> FnOnce<(&'r &Item,)>`
note: this closure does not fulfill the lifetime requirements
   --> src/main.rs:42:41
    |
42  |         let items = items.iter().filter(|item| !item.is_deleted());
    |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^
note: the lifetime requirement is introduced here
   --> /appsrc/.cache/registry/src/github.com-1ecc6299db9ec823/warp-0.3.2/src/filter/mod.rs:259:32
    |
259 |         F::Output: TryFuture + Send,
    |                                ^^^^

Nowhere in the handler is an FnOnce built, nor is one asked for by Iterator::filter() so at the very least the diagnostic is misleading. However, rewriting the code to use a free function instead of a closure allows the code to compile:

fn item_is_not_deleted(item: &&Item) -> bool {
    !item.is_deleted()
}

// Replace line 42 with:

let items = items.iter().filter(item_is_not_deleted);

I can't see any reason why a closure should fail here but a free function should work; I'd expect them to be effectively identical since the closure doesn't capture anything from its environment.

For more specific details, see the repository linked above. (I'm happy to copy the repro code directly into this issue if desired, just let me know.)

@cdhowie cdhowie added the C-bug Category: This is a bug. label Apr 16, 2022
@cdhowie
Copy link
Author

cdhowie commented Apr 17, 2022

While working on the actual code from which this example is derived, I stumbled upon the modification that properly allows the code to compile. This function signature:

async fn write_items<'a, T, P>(p: &P, items: T) -> Result<(), ()>
    where P: Provider,
        T: IntoIterator<Item=&'a Item>

Needs to be changed to:

async fn write_items<'a, T, I, P>(p: &P, items: T) -> Result<(), ()>
    where P: Provider,
        I: Iterator<Item=&'a Item> + Send,
        T: IntoIterator<Item=&'a Item, IntoIter=I>

Constraining IntoIterator::IntoIter to be Send is what's missing, but the output from the compiler is incredibly misleading in this regard as it suggests the problem is the closure passed to Iterator::filter(), and incorrectly names its type.

It's still unclear to me why using a free function allows the code to compile without the Send bound.

I think there is definitely a diagnostic bug here, and possibly another bug highlighted by the free-function workaround.

@csmoe csmoe added the A-async-await Area: Async & Await label Apr 17, 2022
@eholk
Copy link
Contributor

eholk commented Apr 25, 2022

Thanks for the report! It sounds like at the very least we could do better with the diagnostic here and add an explicit hint to add a Send bound. Beyond that, I think we'll need to do some more investigation to see what the compiler's doing and whether that's incorrect. The part about a free function working while the closure doesn't is especially weird.

It'd be nice if we could get a repro without Warp... Maybe that will get easier as we understand the bug a little bit better?

@rustbot label +AsyncAwait-Triaged +E-needs-mcve

@rustbot rustbot added AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example labels Apr 25, 2022
@Skepfyr
Copy link
Contributor

Skepfyr commented Jul 20, 2022

I got some way towards minimising this although I'm pretty sure it still could be simpler than this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c051d45438f4435e551f09dc2a9a3ad9

@Alexendoo Alexendoo removed the E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example label Jul 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await Area: Async & Await AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug.
Projects
None yet
Development

No branches or pull requests

6 participants