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

Generic params from impl dont have 'async_trait lifetime #8

Closed
kpp opened this issue Jul 24, 2019 · 10 comments
Closed

Generic params from impl dont have 'async_trait lifetime #8

kpp opened this issue Jul 24, 2019 · 10 comments

Comments

@kpp
Copy link

kpp commented Jul 24, 2019

I tried to produce a MRE:

#[async_trait]
pub trait MapInto<T>: Future {
    async fn map_into(self) -> T
        where Self: Sized;
}

#[async_trait]
impl<F, T> MapInto<T> for F
    where F: ?Sized,
          F: Future + Send,
          T: From<F::Output>,
{
    async fn map_into(self) -> T
        where Self: Sized,
    {
        self.await.into()
    }
}

error:

error[E0309]: the parameter type `T` may not live long enough
   --> src/future.rs:116:1
    |
116 | #[async_trait]
    | ^^^^^^^^^^^^^^
117 | impl<F, T> MapInto<T> for F
    |         - help: consider adding an explicit lifetime bound `T: 'async_trait`...
    |

A fix for me: T: 'static, in impl block. Is it a bug?

@dtolnay
Copy link
Owner

dtolnay commented Jul 24, 2019

Thanks! I don't know how to fix this best, since I don't want to infer overly restrictive bounds by default -- it's always possible for the user of the macro to add extra bounds as necessary, but there isn't a good way for them to subtract bounds that aren't right. I found that inferring _: 'async_trait for every generic parameter on the impl block tends to be too restrictive.

I would recommend adding your own T: 'async_trait bound (not T: 'static) when you see a compiler error like that one.

#[async_trait]
pub trait MapInto<T>: Future {
    async fn map_into(self) -> T
    where
        Self: Sized,
        T: 'async_trait;
}

#[async_trait]
impl<F, T> MapInto<T> for F
where
    F: ?Sized + Future + Send,
    T: From<F::Output>,
{
    async fn map_into(self) -> T
    where
        Self: Sized,
        T: 'async_trait,
    {
        self.await.into()
    }
}

That bound basically means "T lives \"long enough\"". To explain the underlying lifetime logic a bit:

  • The caller of an async trait method gets to choose any lifetime to use as 'async_trait for that call;

  • We require the caller of the async trait method to guarantee that all arguments they pass outlive 'async_trait;

  • In return, we require that the implementer of the async trait method guarantee that the returned future outlives 'async_trait.

So for example, if the caller wants to get back a 'static future, then they must pass in arguments that are all 'static. This system reflects rustc's lifetime rules for return position impl Trait and async fn.

@kpp
Copy link
Author

kpp commented Jul 24, 2019

I would recommend adding your own T: 'async_trait bound (not T: 'static) when you see a compiler error like that one.

Then I will have to add a lot of T: 'async_trait, E: 'async_trait into my methods =) See kpp/futures-async-combinators@1191ca4

Your solution works:

#[async_trait]
pub trait TryFuture: Future {
    type Ok;
    type Error;

    async fn map_ok<U, F>(self, f: F) -> Result<U, Self::Error>
        where F: FnOnce(Self::Ok) -> U + Send,
              Self: Sized,
              Self::Ok: Send + 'async_trait,
              Self::Error: Send + 'async_trait;
}

#[async_trait]
impl<T, E, Fut> TryFuture for Fut
    where Fut: ?Sized + Future<Output = Result<T, E>> + Send,
{
    type Ok = T;
    type Error = E;

    async fn map_ok<U, F>(self, f: F) -> Result<U, Self::Error>
        where F: FnOnce(Self::Ok) -> U + Send,
              Self: Sized,
              Self::Ok: Send + 'async_trait,
              Self::Error: Send + 'async_trait,
    {
        self.await.map(f)
    }
}

However it requires a lot of boilerplate... I need to figure out how to beautify it.

@Ekleog
Copy link

Ekleog commented Jun 1, 2020

FWIW, here is a more minimal example:

#[async_trait::async_trait]
pub trait Trait<T> {
    async fn foo() {
        ()
    }
}

Seeing as such a trivial example already fails the T: 'async_trait bound… I'd be curious in which cases the bound would not be required?

@taiki-e
Copy link
Contributor

taiki-e commented Jun 14, 2020

FYI: currently, it seems functions that return impl traits capture all generic type parameters in function's generics, whether they are actually used or not. (probably related to rfc1951)
For example:

pub trait Trait<T> {
    fn method<'lt>() -> Box<dyn ::core::fmt::Debug + 'lt>;
}

impl<T> Trait<T> for usize {
    fn method<'lt>() -> Box<dyn ::core::fmt::Debug + 'lt> {
        fn __method<'lt, T>() -> impl ::core::fmt::Debug + 'lt {}
        Box::new(__method::<T>())
        //~^ ERROR the parameter type `T` may not live long enough
    }
}

playground

(EDIT: Seems generic type parameters of functions that return impl traits do not need explicit lifetime bounds because implied lifetime bounds.)

In the current implementation of async-trait, the generated freestanding function's generics is impl's generics + method's generics, so it seems that all generic type parameters actually require (at least 'async_trait) lifetime bounds.

Also, this seems to be specific to generic type parameters of functions that return impl traits, for example, it doesn't apply to lifetime definitions of functions that return impl traits or generic type parameters of functions that return normal values.

(I wrote this comment because I wasn't sure if #8 (comment) was written with this in mind. I'm sorry if I misread your comment.)

@Kestrer
Copy link

Kestrer commented Jul 1, 2020

I'm having a similar issue, but unfortunately this isn't as easily fixable:

#[async_trait]
trait Trait {
    async fn func(&self);
}

struct Struct<R>(R);

#[async_trait]
impl<R: Deref<Target = Option<T>> + Send + Sync, T: Send + Sync> Trait for Struct<R> {
    async fn func(&self) {
        //               ^ [rustc E0309] [E] the parameter type `T` may not live long enough
    }
}

I can't add 'async_trait to T because it doesn't exist at the item scope yet, and I don't want to have to make T: 'static. Is there a fix for this?

@taiki-e
Copy link
Contributor

taiki-e commented Jul 8, 2020

@Koxiaet A workaround that I know is adding lifetime to trait: playground

@the-notable
Copy link

I'm getting the same error, but with default impl directly in declaration:

#[async_trait]
trait Trait <T: Send>: Struct {
    async fn assoc_func<U>(n: Vec<T>) -> CustomResult<'_, Vec<U>> {
        call_async_func().await
    }
}

Produces:

error[E0309]: the parameter type `T` may not live long enough
  --> src/model/mod.rs:84:113
   |
83 |   trait Trait<T: Send>: Struct {
   |               -------- help: consider adding an explicit lifetime bound...: `T: 'async_trait +`
84 |       async fn assoc_func<U>(n: Vec<T>) -> CustomResult<'_, Vec<U>> {
   |  ___________________________________________________________________^
85 | |         call_async_func().await
86 | |     }
   | |_____^ ...so that the type `impl core::future::future::Future` will meet its required lifetime bounds

Addind 'async_trait lifetime to trait:

#[async_trait]
trait Trait <T: 'async_trait + Send>: Struct {
    async fn assoc_func<U>(n: Vec<T>) -> CustomResult<'_, Vec<U>> {
        call_async_func().await
    }
}

Results in:

error[E0261]: use of undeclared lifetime name `'async_trait`
  --> src/model/mod.rs:83:28
   |
83 | pub trait Trait<T: 'async_trait + Send>: Struct{
   |                 -  ^^^^^^^^^^^^ undeclared lifetime
   |                 |
   |                 help: consider introducing lifetime `'async_trait` here: `'async_trait,`

@taiki-e taiki-e mentioned this issue Sep 4, 2020
@taiki-e
Copy link
Contributor

taiki-e commented Apr 28, 2021

@dtolnay: I think this issue has been fixed by #143: playground

@dtolnay
Copy link
Owner

dtolnay commented Apr 29, 2021

Nice!

@jaskij
Copy link

jaskij commented Jun 19, 2023

Sadly, I'm still getting this issue, in a more complicated case (playground link, MRE below). The provided workaround of adding 'async_trait works just fine.

It seems to be some more complex interaction of types, because removing the _output parameter, or removing the default implementation both get rid of the error.

use async_trait::async_trait;
use tokio::sync::mpsc;

#[async_trait]
trait InputStage<StageOutput>: Sized
where
    StageOutput: Send,
{
    async fn run(
        mut self,
        _output: mpsc::Sender<StageOutput>,
    )
    //where StageOutput: 'async_trait,
    {

    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants