rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.69k stars 12.75k forks source link

Wrong Send constraint when using a trait with lifetime + associated type in async #60658

Open carllerche opened 5 years ago

carllerche commented 5 years ago

Update: See https://github.com/rust-lang/rust/issues/60658#issuecomment-1509321859 for the latest reproducer

Minimal:

#![feature(async_await, await_macro)]

use std::future::Future;

pub trait Foo<'a> {
    type Future: Future<Output = ()>;

    fn foo() -> Self::Future;
}

struct MyType<T>;

impl<'a, T> Foo<'a> for MyType<T>
where
    T: Foo<'a>,
    T::Future: Send,
{
    type Future = Box<Future<Output = ()> + Send>;

    fn foo() -> Self::Future {
        Box::new(async move {
            await!(T::foo())
        })
    }
}

fn main() {
}

Out:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/main.rs:21:9
   |
21 | /         Box::new(async move {
22 | |             await!(T::foo())
23 | |         })
   | |__________^ one type is more general than the other
   |
   = note: expected type `std::marker::Send`
              found type `std::marker::Send`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

Compiler version: rustc 1.36.0-nightly (2019-05-07)

Nemo157 commented 5 years ago

Expanding the constraint to

for<'b> <T as Foo<'b>>::Future: Send

gets past this error. I'm not sure why it needs this more general constraint though.

reproduction directly with generators

nikomatsakis commented 5 years ago

I suspect one can make a failure of this with impl trait alone as well, though I've not tried. I'm not sure what exactly the problem is.

nikomatsakis commented 5 years ago

Deferring -- not blocking stabilization. Would be good to investigate though.

cramertj commented 5 years ago

updated version which compiles when async move { T::foo().await } is replaced with T::foo()

dtolnay commented 5 years ago

I hit this in https://github.com/dtolnay/async-trait/issues/34 and minimized to a quite similar looking minimal repro but with the associated type in argument position rather than return position.

I believe this code should compile as written, but right now I am totally stuck on what missing bound is being expected. Is there anything similar to @Nemo157's hrtb that would unblock this one?

use std::future::Future;

pub trait Trait<'a> {
    type Assoc: Send + 'static;

    fn f(x: Self::Assoc) -> Box<dyn Future<Output = ()> + Send>
    where
        'a: 'static,
        Self: Sized + 'static,
    {
        Box::new(f::<Self>(x))
    }
}

async fn f<T: Trait<'static>>(_x: T::Assoc) {
    async {}.await
}
error[E0308]: mismatched types
  --> src/main.rs:11:9
   |
11 |         Box::new(f::<Self>(x))
   |         ^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected type `std::marker::Send`
              found type `std::marker::Send`
jebrosen commented 5 years ago

I keep running into this both with and without async-trait while trying to implement Future-returning traits in Rocket, which makes heavy use of associated types. In particular I'm currently trying to do this: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=034908e77e7fca31dfbc73fb57c2f3e5 (compare to the version with Option that does not have an associated type).

If I or someone else were to try and investigate the cause, is there any good starting point?

chaaz commented 5 years ago

You can get to this error via the use of try_select, try_join, pin_mut and boxed: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0233531e1b7801aca03b4564742d5218. The error given makes it really difficult to figure out what the underlying problem is, and/or what workaround can be done to avoid it. :(

edit: an even more straightforward example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3e0b26047b48a0c62db94939556d4538

jebrosen commented 4 years ago

@stephaneyfx pointed out a workaround (using FutureExt::map) that appears to work in my case:

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=51593d4cd22b7dc0a21d1d7c444d9210

I don't think this helps async_trait, but having this to compare to might help figure out the underlying cause.

estebank commented 4 years ago

Triage, no change in the output.

Ploppz commented 4 years ago

I don't know if it's helpful or to what degree it's related, but I get "one type is more general than the other" error that I fail to understand here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=14c3f1f5a60a8b36a52ab55776ba241e It can be fixed if I use Box::pin instead of .boxed(), which makes me think it's a bug.

benschulz commented 4 years ago

I fail to do the mental gymnastics required to be sure, but this might be a dupe of #64552. That the HRTB works lines up well with Aaron1011's analysis over there.

estebank commented 3 years ago

Current output:

error[E0277]: `(dyn Future<Output = ()> + Send + 'static)` cannot be unpinned
  --> src/main.rs:16:5
   |
16 |     type Future = Box<dyn Future<Output = ()> + Send>;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Unpin` is not implemented for `(dyn Future<Output = ()> + Send + 'static)`
   |
   = note: consider using `Box::pin`
   = note: required because of the requirements on the impl of `Future` for `Box<(dyn Future<Output = ()> + Send + 'static)>`
note: required by a bound in `Foo::Future`
  --> src/main.rs:4:25
   |
4  |     type Future: Future<Output = ()>;
   |                         ^^^^^^^^^^^ required by this bound in `Foo::Future`

error: implementation of `Send` is not general enough
  --> src/main.rs:19:9
   |
19 | /         Box::new(async move {
20 | |             T::foo().await
21 | |         })
   | |__________^ implementation of `Send` is not general enough
   |
   = note: `<T as Foo<'0>>::Future` must implement `Send`, for any lifetime `'0`...
   = note: ...but `Send` is actually implemented for the type `<T as Foo<'a>>::Future`
tmandry commented 1 year ago

Updated reproducer: (playground)

use core::pin::Pin;
use std::future::Future;

pub trait Foo<'a> {
    type Future: Future<Output = ()>;

    fn foo() -> Self::Future;
}

struct MyType<T>(T);

impl<'a, T> Foo<'a> for MyType<T>
where
    T: Foo<'a>,
    T::Future: Send,
{
    type Future = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;

    fn foo() -> Self::Future {
        Box::pin(async move {
            T::foo().await
        })
    }
}

produces:

error: lifetime may not live long enough
  --> src/lib.rs:21:9
   |
12 |   impl<'a, T> Foo<'a> for MyType<T>
   |        -- lifetime `'a` defined here
...
21 | /         Box::pin(async move {
22 | |             T::foo().await
23 | |         })
   | |__________^ cast requires that `'a` must outlive `'static`

error: higher-ranked lifetime error
  --> src/lib.rs:21:9
   |
21 | /         Box::pin(async move {
22 | |             T::foo().await
23 | |         })
   | |__________^
   |
   = note: could not prove `Pin<Box<[async block@src/lib.rs:21:18: 23:10]>>: CoerceUnsized<Pin<Box<(dyn Future<Output = ()> + Send + 'b)>>>`

error: could not compile `playground` due to 2 previous errors

as noted in https://github.com/rust-lang/rust/issues/60658#issuecomment-493043548, replacing T::Future: Send with for<'b> <T as Foo<'b>>::Future: Send makes the error go away.