How can I fix this lifetime?

I cannot understand how to fix the below code: Rust Explorer Playground: Rust Explorer.

How can I fix that lifetime?

/*
[dependencies]
async-trait = "0.1.61"
sqlx = { version = "0.6.2", default-features = false, features = [
  "macros",
  "postgres",
  "runtime-tokio-native-tls",
  "time",
] }
tokio = { version = "1.24.1", features = ["full"] }
*/

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,
        team_id: &str,
        _input: &PlayerInput,
        lambda: &(dyn for<'b> Fn(
            PlayerCreateLambdaArgs<'b>,
        )
            -> Pin<Box<dyn Future<Output = Result<DomainPlayer, String>> + Send + 'b>>
              + Sync
              + '_),
    ) -> Result<DomainPlayer, String>;
}

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

pub struct Repo {
    pub pool: Arc<sqlx::PgPool>,
}

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

#[async_trait::async_trait]
impl Player for Repo {
    async fn player_create(
        &self,
        _team_id: &str,
        _input: &PlayerInput,
        lambda: &(dyn for<'b> Fn(
            PlayerCreateLambdaArgs<'b>,
        )
            -> Pin<Box<dyn Future<Output = Result<DomainPlayer, String>> + Send + 'b>>
              + Sync
              + '_),
    ) -> Result<DomainPlayer, String> {
        let mut tx = self.pool.begin().await.unwrap();

        // use _input here

        let domain_player = lambda(PlayerCreateLambdaArgs {
            shirt_next_value: Box::new(|model: String| {
                self::Shirt::shirt_get_next_and_increase(self, &mut tx, model)
            }),
        })
        .await?;

        let res =
            sqlx::query_as::<_, DomainPlayer>("INSERT INTO player (...) VALUES (...) RETURNING *")
                .bind(domain_player.id)
                .bind(domain_player.shirt_number)
                .fetch_one(&mut *tx)
                .await
                .unwrap();

        Ok(res)
    }
}

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

        // use _tx here...

        let res = 123;

        Ok(res)
    }
}

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

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

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

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

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("team", &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,
                        shirt_number,
                    };

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

        Ok(res)
    }
}

#[tokio::main]
async fn main() -> Result<(), String> {
    let db_conn = sqlx::PgPool::connect("fake_url").await.unwrap();

    let pg_repo = Arc::new(Repo::new(Arc::new(db_conn)));

    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:

error[E0507]: cannot move out of `input.id` which is behind a shared reference
   --> src/main.rs:151:29
    |
151 |                         id: input.id,
    |                             ^^^^^^^^ move occurs because `input.id` has type `std::string::String`, which does not implement the `Copy` trait

error: lifetime may not live long enough
   --> src/main.rs:147:17
    |
140 |       pub async fn handle(&self, input: &PlayerInput) -> Result<DomainPlayer, String> {
    |                                         - let's call the lifetime of this reference `'1`
...
147 | /                 Box::pin(async move {
148 | |                     let shirt_number = (args.shirt_next_value)("player".to_string()).await?;
149 | |
150 | |                     let o = DomainPlayer {
...   |
155 | |                     Ok(o)
156 | |                 })
    | |__________________^ returning this value requires that `'1` must outlive `'static`

For more information about this error, try `rustc --explain E0507`.

@vague in this version I fixed almost any issue. I'm using FnOnce not FnMut and HRTB as you suggested.

The only issue is I cannot use input.

Can you find a way please?

Is it possible to delete some code and have a shorter minimal example ? I was going to take a stab at the problem, but 150+ lines is quite intimidating.

This error is easy to fix:

let id = input.id.clone();

Box::pin(async move {
  let shirt_number = (args.shirt_next_value)("player".to_string()).await?;
  let o = DomainPlayer { id, shirt_number };
  Ok(o)
})

Nope. I need to use input entirely. Not just id. This is the issue.

I tried but if I touch just one part of this code I can't make it go wrong like it happens in my real project. You can use the rust explorer link to see the code interactively: Rust Explorer

@vague I think this signature is the issue with the usage of input.

But I cannot understand how to fix.

I simplified a lot the code of the other example. Rust Explorer Playground here: Rust Explorer.

@zeroexcuses I simplified a lot the code of the other example. Rust Explorer Playground here: Rust Explorer.

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.