0

I'm trying to use async closure as lambda functions.

But I just found out that there is something more broken that I don't understand:

REPL: https://www.rustexplorer.com/b/w6fmvw.

Error:

error: lifetime may not live long enough
   --> src/main.rs:101:17
    |
94  |       async fn handle(&self, input: &PlayerChangeNameInput, whoami: &str) -> Result<Player, ()> {
    |                                     - lifetime `'life1` defined here
...
101 | /                 Box::pin(async {
102 | |                     let o = Player {
103 | |                         id: musts.actual.id.clone(),
104 | |                         name: input.name.to_string(),
...   |
107 | |                     Ok(o)
108 | |                 })
    | |__________________^ returning this value requires that `'life1` must outlive `'static`

Code:

/*
[dependencies]
async-trait = { version = "0.1.68" }
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
*/

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

trait Trait: Send + Sync + PlayerTrait {}

impl<T: PlayerTrait> Trait for T {}

#[async_trait::async_trait]
trait PlayerTrait: Send + Sync {
    async fn player_change_name(
        &self,
        input: &PlayerChangeNameInput,
        lambda: &(dyn for<'a> Fn(
            PlayerMusts<'a>,
        ) -> Pin<Box<dyn Future<Output = Result<Player, ()>> + Send + 'a>>
              + Sync),
    ) -> Result<Player, ()>;
}

struct PlayerChangeNameInput {
    pub name: String,
}

struct Player {
    pub id: String,
    pub name: String,
}

struct PlayerMusts<'a> {
    pub actual: &'a Player,
    // pub other: &'a str,
}

#[async_trait::async_trait]
impl PlayerTrait for Repo {
    async fn player_change_name(
        &self,
        input: &PlayerChangeNameInput,
        lambda: &(dyn for<'a> Fn(
            PlayerMusts<'a>,
        ) -> Pin<Box<dyn Future<Output = Result<Player, ()>> + Send + 'a>>
              + Sync),
    ) -> Result<Player, ()> {
        // I need input here
        dbg!(&input.name);

        // I need to await for many things in here

        // Eg. for DB connection pool...
        // let mut db_connection = pool.begin().await?;

        // Here I can query the actual player, I'm faking it now...
        let actual = Player {
            id: "1".to_string(),
            name: "Bob".to_string(),
        };

        let worked_player = lambda(PlayerMusts {
            actual: &actual,
            // other: "Other",
        })
        .await?;

        // I'm saving the lambda result here, I'm faking it now...
        // let result = db_connection.save(&worked_player, &actual).await?;
        let result = worked_player;

        // db_connection.save_and_close().await?;

        Ok(result)
    }
}

struct HandlerImpl {
    repo: Arc<Repo>,
}

fn new_handler(repo: Arc<Repo>) -> Box<dyn Handler> {
    Box::new(HandlerImpl { repo })
}

#[async_trait::async_trait]
trait Handler: Send + Sync {
    async fn handle(&self, input: &PlayerChangeNameInput, whoami: &str) -> Result<Player, ()>;
}

#[async_trait::async_trait]
impl Handler for HandlerImpl {
    async fn handle(&self, input: &PlayerChangeNameInput, whoami: &str) -> Result<Player, ()> {
        // use whoami here...
        dbg!(whoami);

        let result = self
            .repo
            .player_change_name(input, &|musts| {
                Box::pin(async {
                    let o = Player {
                        id: musts.actual.id.clone(),
                        name: input.name.to_string(),
                    };

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

        Ok(result)
    }
}

pub struct Repo {
    // pub pool: Arc<DbPool>,
}

impl Repo {
    pub fn new() -> Self {
        Self {}
    }
}

#[tokio::main]
async fn main() -> Result<(), ()> {
    let db_repo = Arc::new(Repo::new());

    let handler = new_handler(db_repo);

    let whoami = "Myself";

    let fake_input = &PlayerChangeNameInput{name: "Frank".to_string()};

    let result = handler.handle(fake_input, whoami).await?;

    dbg!(result.name);

    Ok(())
}

UPDATE:

I just found out another quirk with the for<'a> way: https://www.rustexplorer.com/b/vwnvu9

error[E0582]: binding for associated type `Output` references lifetime `'b`, which does not appear in the trait input types
  --> src/main.rs:14:29
   |
14 |     dyn for<'b> Fn(ArgT) -> Pin<Box<dyn Future<Output = Result<ResT, ()>> + Send + 'b>> + Sync;
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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

Which is really strange... to say the least.

Fred Hors
  • 3,258
  • 3
  • 25
  • 71
  • This is the same problem as [this question](https://stackoverflow.com/q/67569562/501250) for which there isn't a good solution yet. There are a few things you can do that will incidentally fix this in your code by working around the problem instead of actually solving it using lifetimes. – cdhowie Mar 29 '23 at 01:31
  • It can be solved by adding a lifetime and a dummy parameter, https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=66c84587c16189d5b4dd4ade0f8ac891. – Chayim Friedman Mar 29 '23 at 04:32
  • `There are a few things you can do that will incidentally fix this in your code by working around the problem instead of actually solving it using lifetimes.`. For example how? – Fred Hors Mar 29 '23 at 11:25
  • @ChayimFriedman thank you. But why this hack? Is there an issue to subscribe to on Rust github to follow new on this topic? – Fred Hors Mar 29 '23 at 11:29
  • Guys, what about the error in the UPDATED question? – Fred Hors Mar 29 '23 at 11:29
  • This is a hack to add `where` clauses to HRTB. [I explained how it works here](https://stackoverflow.com/questions/72657504/function-taking-an-async-closure-that-takes-a-reference-and-captures-by-referenc/72673740#72673740). I don't know an issue you can follow, but you can search for that. – Chayim Friedman Mar 29 '23 at 11:37
  • @ChayimFriedman I read https://stackoverflow.com/questions/72657504/function-taking-an-async-closure-that-takes-a-reference-and-captures-by-referenc/72673740#72673740 but I still don't know how to change the lambda type, can you help me? https://www.rustexplorer.com/b/vwnvu9 – Fred Hors Mar 29 '23 at 11:52
  • Just don't define a type alias. – Chayim Friedman Mar 29 '23 at 12:27
  • I'm using it many times in the code. What you see here is just an excerpt unfortunately. – Fred Hors Mar 29 '23 at 12:29
  • Why can't I use the type alias? – Fred Hors Mar 29 '23 at 12:29
  • It's crazy that your code works, @ChayimFriedman, but I cannot use it with the type alias. I tried everything! – Fred Hors Mar 29 '23 at 13:01
  • You cannot use it with a type alias, that's true. Because the alias will need somehow to pass the HRTB lifetime to its generic parameter, and it can't. – Chayim Friedman Mar 29 '23 at 13:05

0 Answers0