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
Tracked by #110338
stephaneyfx opened this issue Apr 30, 2020 · 16 comments
Open
Tracked by #110338

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

stephaneyfx opened this issue Apr 30, 2020 · 16 comments
Assignees
Labels
A-async-await Area: Async & Await A-typesystem Area: The type system 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

@stephaneyfx
Copy link
Contributor

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 Category: This is a bug. label Apr 30, 2020
@jonas-schievink jonas-schievink added A-async-await Area: Async & Await A-typesystem Area: The type system T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Apr 30, 2020
@stephaneyfx
Copy link
Contributor Author

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

#71671 Because I like link

@nikomatsakis
Copy link
Contributor

@rustbot claim

I will attempt to investigate.

@tmandry tmandry added the AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. label May 5, 2020
@EliSnow
Copy link

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

@nikomatsakis
Copy link
Contributor

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 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 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 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
Member

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

@rustbot claim

@estebank
Copy link
Contributor

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

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

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 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 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 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.

Theodus added a commit to edgeandnode/gateway that referenced this issue Apr 12, 2023
This tweaks the Agora context type we use in order to avoid a recurring
Rust compiler bug (rust-lang/rust#71723) which
makes it suddenly impossible to carry the context across an await. This
comes at the cost of increasing String allocations. Though the use of
snmalloc mitigates this cost somewhat.
Theodus added a commit to edgeandnode/gateway that referenced this issue Apr 12, 2023
This tweaks the Agora context type we use in order to avoid a recurring
Rust compiler bug (rust-lang/rust#71723) which
makes it suddenly impossible to carry the context across an await. This
comes at the cost of increasing String allocations. Though the use of
snmalloc mitigates this cost somewhat.
Theodus added a commit to edgeandnode/gateway that referenced this issue Apr 13, 2023
This tweaks the Agora context type we use in order to avoid a recurring
Rust compiler bug (rust-lang/rust#71723) which
makes it suddenly impossible to carry the context across an await. This
comes at the cost of increasing String allocations. Though the use of
snmalloc mitigates this cost somewhat.
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-typesystem Area: The type system 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
Status: In progress (current sprint)
Development

Successfully merging a pull request may close this issue.