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

Weird type inference around impl Trait and async #124757

Open
Tracked by #110338
Pzixel opened this issue May 5, 2024 · 3 comments
Open
Tracked by #110338

Weird type inference around impl Trait and async #124757

Pzixel opened this issue May 5, 2024 · 3 comments
Labels
A-async-await Area: Async & Await A-inference Area: Type inference AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@Pzixel
Copy link

Pzixel commented May 5, 2024

Following code doesn't work on all the compilers I've checked, e.g. 1.80.0-nightly

#![allow(warnings)]
use std::{collections::HashSet, future::Future};

trait MyTrait {
    fn blah(&self, x: impl Iterator<Item = u32>) -> impl Future<Output = ()> + Send;
}

fn foo<T: MyTrait + Send + Sync>(val: T, unique_x: HashSet<u32>) -> impl Future<Output = ()> + Send {
    let cached = HashSet::new();
    async move {
        let xs = unique_x.union(&cached)
            // .copied() // works
            .map(|x| *x) // error
            ;
        let blah = val.blah(xs.into_iter()).await;
    }
}

This is surprising because a lot of alterations to this code fixes the issue, for example replacing map(|x| *x) with copied(), which is seemingly an equivalent replacement. Moreover, replacing impl Future with Self::Future also solves the problem:

#![allow(warnings)]
use std::{collections::HashSet, future::Future};

trait MyTrait {
    type F: Future<Output = ()> + Send;
    fn blah(&self, x: impl Iterator<Item = u32>) -> Self::F;
}

fn foo<T: MyTrait + Send + Sync>(val: T, unique_x: HashSet<u32>) -> impl Future<Output = ()> + Send {
    let cached = HashSet::new();
    async move {
        let xs = unique_x.union(&cached).map(|x| *x);
        let blah = val.blah(xs.into_iter()).await;
    }
}

Also the error text is very cryptic:

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:10:5
   |
10 | /     async move {
11 | |         let xs = unique_x.union(&cached).map(|x| x.len().to_string())
12 | |             //.collect::<Vec<_>>()
13 | |             ;
14 | |         let blah = val.blah(xs.into_iter()).await;
15 | |     }
   | |_____^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'0 String) -> String` must implement `FnOnce<(&'1 String,)>`, for any two lifetimes `'0` and `'1`...
   = note: ...but it actually implements `FnOnce<(&String,)>`

And because of how async works it can easily leak across the functions:

#![allow(warnings)]
use std::{collections::HashSet, future::Future};

trait MyTrait {
    fn blah(&self, x: impl Iterator<Item = String>) -> impl Future<Output = ()> + Send;
}

async fn foo<T: MyTrait + Send + Sync>(val: T, unique_x: HashSet<String>) {
    let cached = HashSet::new();
    let xs = unique_x.union(&cached).map(|x| x.len().to_string());
    let blah = val.blah(xs.into_iter()).await;
}

fn bar<T: MyTrait + Send + Sync>(val: T, unique_x: HashSet<String>) -> impl Future<Output = ()> + Send  {
    foo(val, unique_x)
}

This function reports

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:15:5
   |
15 |     foo(val, unique_x)
   |     ^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'0 String) -> String` must implement `FnOnce<(&'1 String,)>`, for any two lifetimes `'0` and `'1`...
   = note: ...but it actually implements `FnOnce<(&String,)>`

In a function without a single lifetime argument. You may get some very confusing on the very top of your async stack because somewhere underneath there is one future that violates the Send constraint.

I'm not entirely sure if this is a diganostic error or a compiler issue, but i believe that the original code with impl Future should have been managed to infer that the resulting future is Send. the fact that this doesn't work:

trait MyTrait {
    fn blah(&self, x: impl Iterator<Item = String>) -> impl Future<Output = ()> + Send;
}

But this does

trait MyTrait {
    type F: Future<Output = ()> + Send;
    fn blah(&self, x: impl Iterator<Item = String>) -> Self::F;
}

Makes me think that this have to be a compiler issue

@Pzixel Pzixel added the C-bug Category: This is a bug. label May 5, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label May 5, 2024
@compiler-errors
Copy link
Member

This is #110338, probably #110339.

@Pzixel
Copy link
Author

Pzixel commented May 5, 2024

#110339 looks different, at least I couldn't fix it with extra async blocks, #110338 is a very big umbrella issue, so maybe some of issues listed in there relate to this.

@jieyouxu jieyouxu added A-inference Area: Type inference T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-async-await Area: Async & Await and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels May 13, 2024
@traviscross
Copy link
Contributor

@rustbot labels +AsyncAwait-Triaged

We discussed this in our meeting. It's definitely a case of #110338. It's probably a duplicate of one of them, but we're not positive which, so we've added it to the list there.

@rustbot rustbot added the AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. label Jun 6, 2024
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 A-inference Area: Type inference AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

5 participants