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

Async function leads to a "more general type" error #71723

Open
stephaneyfx opened this issue Apr 30, 2020 · 16 comments
Open

Async function leads to a "more general type" error #71723

stephaneyfx opened this issue Apr 30, 2020 · 16 comments
Assignees
Labels
A-async-await A-typesystem AsyncAwait-Triaged C-bug T-compiler

Comments

@stephaneyfx
Copy link
Contributor

@stephaneyfx stephaneyfx commented Apr 30, 2020

Rustc complains about a type being more general than the other when testing if the Future returned by an async function is Send. The same code without async sugar is accepted by the compiler.

This might be related to #60658.

I tried to minimize further, but removing .flatten(), the boxing with a trait object or even the mapping to an empty stream makes the code compile.

Playground

use futures::{
    future::ready,
    stream::{empty, iter},
    Stream, StreamExt,
};
use std::future::Future;
use std::pin::Pin;

fn is_send<T: Send>(_: T) {}

pub fn test() {
    let _ = is_send(make_future()); // Ok
    let _ = is_send(make_future_2());
}

fn make_future() -> impl Future<Output = ()> {
    iter(Some({
        let s = empty::<()>();
        Box::pin(s) as Pin<Box<dyn Stream<Item = ()> + Send + 'static>>
    }))
    .map(|_| empty::<()>())
    .flatten()
    .for_each(|_| ready(()))
}

// Same as make_future, just async
async fn make_future_2() {
    iter(Some({
        let s = empty::<()>();
        Box::pin(s) as Pin<Box<dyn Stream<Item = ()> + Send + 'static>>
    }))
    .map(|_| empty::<()>())
    .flatten()
    .for_each(|_| ready(()))
    .await
}

Expected result

It compiles successfully -- is_send(make_future()) and is_send(make_future_2()) are both accepted by the compiler.

Actual result

is_send(make_future_2()) is rejected with the following error:

error[E0308]: mismatched types
  --> src/lib.rs:13:13
   |
13 |     let _ = is_send(make_future_2());
   |             ^^^^^^^ one type is more general than the other
   |
   = note: expected type `std::ops::FnOnce<(std::pin::Pin<std::boxed::Box<dyn futures_core::stream::Stream<Item = ()> + std::marker::Send>>,)>`
              found type `std::ops::FnOnce<(std::pin::Pin<std::boxed::Box<dyn futures_core::stream::Stream<Item = ()> + std::marker::Send>>,)>`
@stephaneyfx stephaneyfx added the C-bug label Apr 30, 2020
@jonas-schievink jonas-schievink added A-async-await A-typesystem T-compiler labels Apr 30, 2020
@stephaneyfx
Copy link
Contributor Author

@stephaneyfx stephaneyfx commented Apr 30, 2020

Workaround: Moving the code creating the boxed stream to its own function also makes the code compile.
Playground

use futures::{
    future::ready,
    stream::{empty, iter},
    Stream, StreamExt,
};
use std::pin::Pin;

fn is_send<T: Send>(_: T) {}

pub fn test() {
    let _ = is_send(make_future_2());
}

async fn make_future_2() {
    iter(Some(make_stream()))
    .map(|_| empty::<()>())
    .flatten()
    .for_each(|_| ready(()))
    .await
}

fn make_stream() -> impl Stream<Item = ()> {
    let s = empty::<()>();
    Box::pin(s) as Pin<Box<dyn Stream<Item = ()> + Send + 'static>>
}

@Stargateur
Copy link
Contributor

@Stargateur Stargateur commented May 5, 2020

#71671 Because I like link

@tmandry tmandry added this to On deck in wg-async work via automation May 5, 2020
@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented May 5, 2020

@rustbot claim

I will attempt to investigate.

@tmandry tmandry moved this from On deck to Claimed in wg-async work May 5, 2020
@tmandry tmandry added the AsyncAwait-Triaged label May 5, 2020
@EliSnow
Copy link

@EliSnow EliSnow commented May 21, 2020

I'm also encountering this in my project. I've spent the last two days trying all sorts of combinations to figure out what was going on to no avail. Then I found this issue. Thank you @stephaneyfx for the workaround. I guess I need to ditch async/await for this particular function.

Also note: having the function return impl Future<...> and wraping the async/await stuff in a block also repros the error https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=99744f965c62032dec5aac3e7b5795ca

@tmandry tmandry moved this from Claimed to In progress in wg-async work Jun 16, 2020
@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Jun 16, 2020

Did a bit of digging around. I didn't quite figure out what's going on yet, but a few notes:

First, removing either of these calls makes the error go away

        .map(|_| empty::<()>())
        .flatten()

I'm feeling a bit confused about the types that I'm getting out right now. It looks like the error is somehow coming up around the "lifetime bound" that we get on the dyn Stream type.

@Vooblin
Copy link
Contributor

@Vooblin Vooblin commented Dec 8, 2020

Hi! I would like to try and fix this problem, but not sure if anyone else is doing this. @csmoe @camelid @estebank I saw you were assigned to the mentioned issues, do you work on this problem or I can take it?

@camelid
Copy link
Member

@camelid camelid commented Dec 8, 2020

I'm not sure about the others, but I haven't gotten around to it, so you working on it is fine with me!

@Vooblin
Copy link
Contributor

@Vooblin Vooblin commented Dec 10, 2020

Okay, then I'm assigning myself to this problem. If anyone is working on this I will unassign myself

@rustbot claim

@tmandry
Copy link
Contributor

@tmandry tmandry commented Jul 2, 2021

Doing triage cleanup.

@rustbot release-assignment

@Vooblin if you're still working on this, feel free to re-claim

@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Sep 30, 2021

@rustbot claim

@eholk eholk moved this from On deck to In progress (current sprint) in wg-async work Sep 30, 2021
@estebank
Copy link
Contributor

@estebank estebank commented Sep 30, 2021

Current output:

error: implementation of `FnOnce` is not general enough
  --> src/lib.rs:13:13
   |
13 |     let _ = is_send(make_future_2());
   |             ^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(Pin<Box<(dyn futures::Stream<Item = ()> + std::marker::Send + '0)>>) -> futures::stream::Empty<()>` must implement `FnOnce<(Pin<Box<(dyn futures::Stream<Item = ()> + std::marker::Send + '1)>>,)>`, for any two lifetimes `'0` and `'1`...
   = note: ...but it actually implements `FnOnce<(Pin<Box<dyn futures::Stream<Item = ()> + std::marker::Send>>,)>`

@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Nov 10, 2021

I was not able to reproduce the original errors, but this (somewhat minimized) test does reproduce:

use futures::{
    future::ready,
    stream::{empty, iter},
    Stream, StreamExt,
};
use std::pin::Pin;

fn is_send<T: Send>(_: T) {}

pub fn test() {
    let _ = is_send(make_future_2());
}

// Same as make_future, just async
async fn make_future_2() {
    iter(Some({
        let s = empty::<()>();
        Box::pin(s) as Pin<Box<dyn Stream<Item = ()> + Send + 'static>>
    }))
    .map(|_| empty::<()>())
    .flatten()
    .for_each(|_| ready(()))
    .await
}

fn main() {}

I get the following error now:

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:11:13
   |
11 |     let _ = is_send(make_future_2());
   |             ^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(Pin<Box<(dyn futures::Stream<Item = ()> + std::marker::Send + '0)>>) -> futures::stream::Empty<()>` must implement `FnOnce<(Pin<Box<(dyn futures::Stream<Item = ()> + std::marker::Send + '1)>>,)>`, for any two lifetimes `'0` and `'1`...
   = note: ...but it actually implements `FnOnce<(Pin<Box<dyn futures::Stream<Item = ()> + std::marker::Send>>,)>`

@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Nov 10, 2021

with -Zverbose:

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:11:13
   |
11 |     let _ = is_send(make_future_2());
   |             ^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(Pin<Box<(dyn futures::Stream<Item = ()> + std::marker::Send + '0)>>) -> futures::stream::Empty<()>` must implement `FnOnce<(Pin<Box<(dyn futures::Stream<Item = ()> + std::marker::Send + '1)>>,)>`, for any two lifetimes `'0` and `'1`...
   = note: ...but it actually implements `FnOnce<(Pin<Box<(dyn futures::Stream<Item = ()> + std::marker::Send + RePlaceholder(Placeholder { universe: U6, name: BrAnon(7) }))>>,)>`

@shepmaster
Copy link
Member

@shepmaster shepmaster commented Nov 24, 2021

Another reproduction:

use futures::FutureExt; // 0.3.17
use std::future::Future;

fn foo() -> impl Future<Output = ()> + Send + Sync + 'static {
    async move {
        let work1 = async { "bang" };
        let work1 = work1.map(drop);
        let _error1 = futures::join!(work1);
    }
}
  • Removing the .map(drop) line allows the code to compile.
  • Changing "bang" to 1 or () allows the code to compile.

@compiler-errors
Copy link
Member

@compiler-errors compiler-errors commented Dec 30, 2021

This has bitten me a few times, so I sat down and tried to work on this. I think I've written an (at least partial) solution towards this issue.

I'll put up a (WIP) PR tomorrow or so, so people can take a look at the implementation.

cc: @nikomatsakis, who owns this issue currently

@dralley
Copy link
Contributor

@dralley dralley commented Jul 11, 2022

Just pointing this out, #75791 which was closed as a duplicate of this bug provides a much more straightforward example that doesn't involve async

In case that helps someone with this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await A-typesystem AsyncAwait-Triaged C-bug T-compiler
Projects
wg-async work
In progress (current sprint)
Development

Successfully merging a pull request may close this issue.