How to fix lifetimes here?

I'm using the below code in my project (obviously across many modules).

Why the below error? I cannot understand how to fix it.

Rust playground here.

use std::{future::Future, pin::Pin, sync::Arc};

pub trait Trait: Send + Sync + Player + Shirt {}

impl<T: Player + Shirt> Trait for T {}

#[async_trait::async_trait]
pub trait Player: Send + Sync {
    async fn player_create<'a>(
        &'a self,
        input: &PlayerInput,
        lambda: &Lambda<PlayerCreateLambdaArgs, DomainPlayer>,
    ) -> Result<DomainPlayer, String>;
}

#[async_trait::async_trait]
pub trait Shirt: Send + Sync {
    async fn shirt_get_next_and_increase<'a>(
        &'a self,
        _db_tx: String,
        model: String,
    ) -> Result<i64, String>;
}

pub struct Repo {
    pub pool: Arc<String>,
}

impl Repo {
    pub fn new(pool: Arc<String>) -> Self {
        Self { pool }
    }
}

#[async_trait::async_trait]
impl Player for Repo {
    async fn player_create(
        &self,
        _input: &PlayerInput,
        lambda: &Lambda<PlayerCreateLambdaArgs, DomainPlayer>,
    ) -> Result<DomainPlayer, String> {
        // use _input here

        // let new db_tx here

        let domain_player = lambda(PlayerCreateLambdaArgs {
            shirt_next_value: Box::new(|model: String| {
                Box::pin(self::Shirt::shirt_get_next_and_increase(
                    self,
                    "fake_db_tx".to_string(),
                    model,
                ))
            }),
        })
        .await?;

        let res = DomainPlayer {
            id: domain_player.id,
            shirt_number: domain_player.shirt_number,
        };

        Ok(res)
    }
}

#[async_trait::async_trait]
impl Shirt for Repo {
    async fn shirt_get_next_and_increase(
        &self,
        _db_tx: String,
        _model: String,
    ) -> Result<i64, String> {
        // Here I'm awaiting an async call for DB operations using the same DB transacion of the caller (db_tx)...

        let res = 123;

        Ok(res)
    }
}

pub type Lambda<'a, ArgT, ResT> =
    dyn 'a + Fn(ArgT) -> Pin<Box<dyn Future<Output = Result<ResT, String>> + Send + 'a>> + Sync;

pub struct Needs {
    pub command_pg_repo: Arc<dyn Trait>,
}

#[derive(Default)]
pub struct PlayerInput {
    pub id: String,
}

#[derive(Debug, Default, Clone)]
pub struct DomainPlayer {
    pub id: String,
    pub shirt_number: i64,
}

pub struct PlayerCreateLambdaArgs {
    // other needed fields here
    pub shirt_next_value: Box<
        dyn Fn(String) -> Pin<Box<dyn Future<Output = Result<i64, String>> + Send>> + Send + Sync,
    >,
}

pub struct Handler {
    needs: Arc<Needs>,
}

impl Handler {
    pub fn new(needs: Arc<Needs>) -> Self {
        Self { needs }
    }

    pub async fn handle(&self, input: &PlayerInput) -> Result<DomainPlayer, String> {
        let res = self
            .needs
            .command_pg_repo
            .player_create(&input, &|args| {
                let input = input;

                Box::pin(async move {
                    let shirt_number = (args.shirt_next_value)("player".to_string()).await?;

                    let o = DomainPlayer {
                        id: input.id.to_string(),
                        shirt_number,
                    };

                    Ok(o)
                })
            })
            .await?;

        Ok(res)
    }
}

#[tokio::main]
async fn main() -> Result<(), String> {
    let pg_repo = Arc::new(Repo::new(Arc::new("fake DB pool".to_string())));

    let needs = Arc::new(Needs {
        command_pg_repo: pg_repo,
    });

    let handler = Handler::new(needs);

    let new_player_input = PlayerInput {
        id: "abc".to_string(),
    };

    let player = handler.handle(&new_player_input).await?;

    dbg!(player);

    Ok(())
}

The error:

Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
  --> src/main.rs:47:31
   |
37 |       async fn player_create(
   |       ----- lifetime `'life0` defined here
...
47 |               shirt_next_value: Box::new(|model: String| {
   |  _______________________________^
48 | |                 Box::pin(self::Shirt::shirt_get_next_and_increase(
49 | |                     self,
50 | |                     "fake_db_tx".to_string(),
51 | |                     model,
52 | |                 ))
53 | |             }),
   | |______________^ cast requires that `'life0` must outlive `'static`

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

When you use a trait object inside a type that has no lifetime parameter (e.g. Box or Rc), then dyn Trait is implicitly dyn Trait + 'static. It doesn't look like your lambda can be 'static (since it captures the self reference). So you should probably change your PlayerCreateLambdaArgs type and in particular the type of its shirt_next_value field to include a lifetime parameter.

2 Likes

Thanks @H2CO3, I tried with a lietime too but with no luck unfortunately:

Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
  --> src/main.rs:47:31
   |
37 |       async fn player_create(
   |       ----- lifetime `'life0` defined here
...
40 |           lambda: &Lambda<PlayerCreateLambdaArgs, DomainPlayer>,
   |           ------ has type `&dyn Fn(PlayerCreateLambdaArgs<'1>) -> Pin<Box<dyn Future<Output = Result<DomainPlayer, String>> + Send>> + Sync`
...
47 |               shirt_next_value: Box::new(|model: String| {
   |  _______________________________^
48 | |                 Box::pin(self::Shirt::shirt_get_next_and_increase(
49 | |                     self,
50 | |                     "fake_db_tx".to_string(),
51 | |                     model,
52 | |                 ))
53 | |             }),
   | |______________^ this usage requires that `'life0` must outlive `'1`

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

This seems to work:

#[async_trait::async_trait]
pub trait Player: Send + Sync {
    async fn player_create<'a>(
        &'a self,
        input: &PlayerInput,
-       lambda: &Lambda<PlayerCreateLambdaArgs, DomainPlayer>,
+       lambda: &Lambda<PlayerCreateLambdaArgs<'a>, DomainPlayer>,
    ) -> Result<DomainPlayer, String>;
}

Rust Playground

I haven’t understood the lifetimes well enough to determine whether this change could lead to problems down the line for callers of the API; the one use-case included in the playground works for now though.

3 Likes

This works! Thanks! In my real code the problem was similar. Thanks!

  1. The closure captures &'a self, so the closure must outlives 'a, i.e. the closure at least has a 'a bound connected to &'a self.
  1. But the closure is defined to have a 'static lifetime bound, because the lifetime elision on trait objects here[1] is
pub struct PlayerCreateLambdaArgs {
    pub shirt_next_value: Box<
        dyn 'static + Fn(String) -> Pin<Box<dyn 'static + Future<Output = Result<i64, String>> + Send>> + Send + Sync,
    >,
}
  1. &'a self is any refecerence of Self, so it can't be 'static, which leads to the error you see now.

  2. The solution is given by @steffahn . The steps are

    • let shirt_next_value (thus PlayerCreateLambdaArgs) contain a lifetime parameter to override the default 'static elision
    • to express the lifetime connection between &'a self and PlayerCreateLambdaArgs<'a>

    • If the trait has no lifetime bounds, then the lifetime is inferred in expressions and is 'static outside of expressions.
    ↩︎

In case someone is interested in the lifetime annotation life0.

  1. If you comment out the erroneous code and expand #[async_trait]
#[async_trait::async_trait]
pub trait Player: Send + Sync {
    async fn player_create<'a>(
        &'a self,
        input: &PlayerInput,
        lambda: &Lambda<PlayerCreateLambdaArgs, DomainPlayer>,
    ) -> Result<DomainPlayer, String>;
}

You'll get

pub trait Player: Send + Sync {
    #[must_use]
    #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)]
    fn player_create<'a, 'life0, 'life1, 'async_trait>(
        &'a self,
        input: &'life0 PlayerInput,
        lambda: &'life1 Lambda<PlayerCreateLambdaArgs, DomainPlayer>,
    ) -> ::core::pin::Pin<
        Box<
            dyn ::core::future::Future<Output = Result<DomainPlayer, String>>
                + ::core::marker::Send
                + 'async_trait,
        >,
    >
    where
        'a: 'async_trait,
        'life0: 'async_trait,
        'life1: 'async_trait,
        Self: 'async_trait;
}
  1. But the life0 here is not the lifetime annotation in input: &'life0 PlayerInput, because the erroneous code happens in
#[async_trait::async_trait]
impl Player for Repo {
    async fn player_create(
        &self,
        _input: &PlayerInput,
        lambda: &Lambda<PlayerCreateLambdaArgs, DomainPlayer>,
    ) -> Result<DomainPlayer, String> {

Attention to the elided &'a self. So the actual life0 is on &self :

    fn player_create<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 self,
        input: &'life1 PlayerInput,
        lambda: &'life2 Lambda<PlayerCreateLambdaArgs, DomainPlayer>,
    ) -> ::core::pin::Pin<
        Box<
            dyn ::core::future::Future<Output = Result<DomainPlayer, String>>
                + ::core::marker::Send
                + 'async_trait,
        >,
    >
    where ...

Thank you very much.

I opened a very slightly variant here: Why it is so difficult to pass this &mut db transaction to this closure? because the real issue is using a db transaction in that closure.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.