0

I'm trying to reproduce an error I'm having on my small app in Rust.

I'm trying to use async closure as lambda functions (very tipical in other languages).

But I just found out that this is a bit difficult in Rust:

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

Error:

error: lifetime may not live long enough
  --> src/main.rs:55:9
   |
54 |       let result = player_change_name(&|musts| {
   |                                         ------ return type of closure is Pin<Box<(dyn Future<Output = Result<Player, ()>> + Send + '2)>>
   |                                         |
   |                                         has type `PlayerMusts<'1>`
55 | /         Box::pin(async {
56 | |             let o = Player {
57 | |                 id: musts.actual.id.clone(),
58 | |                 name: "Frank".to_string(),
...  |
62 | |             Ok(o)
63 | |         })
   | |__________^ returning this value requires that `'1` must outlive `'2`

Code:

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

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

#[derive(Debug, Default, Clone)]
pub struct Player {
    pub id: String,
    pub name: String,
    pub team: Option<String>,
}

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

async fn player_change_name<'a>(
    lambda: &(dyn 'a
          + Fn(PlayerMusts) -> Pin<Box<dyn Future<Output = Result<Player, ()>> + Send + 'a>>
          + Sync),
) -> Result<Player, ()> {
    // 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(),
        team: None,
    };

    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)
}

#[tokio::main]
async fn main() -> Result<(), ()> {
    let result = player_change_name(&|musts| {
        Box::pin(async {
            let o = Player {
                id: musts.actual.id.clone(),
                name: "Frank".to_string(),
                team: None,
            };

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

    dbg!(result);

    Ok(())
}
Fred Hors
  • 3,258
  • 3
  • 25
  • 71

1 Answers1

1

The problem here is putting the lifetime on player_change_name. This function should not accept a lifetime parameter, because it's too restrictive at this point -- the caller has to choose the lifetime, and there is no possible lifetime the caller can choose that will make sense.

The function itself doesn't capture the lifetime 'a either, so that restriction is also unnecessary.

What you want to say regarding the lifetimes is that "for any possible lifetime 'a, this closure should accept a PlayerMusts<'a> and return a future that captures 'a. You need a higher-rank trait bound or HRTB:

async fn player_change_name(
    lambda: &(dyn for<'a> Fn(PlayerMusts<'a>) -> Pin<Box<dyn Future<Output = Result<Player, ()>> + Send + 'a>> + Sync),
) -> Result<Player, ()> {

Note the for<'a> syntax, which creates the HRTB.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Thanks. I tried, but I think something is still broken: https://www.rustexplorer.com/b/hsdahn. – Fred Hors Mar 28 '23 at 22:43
  • Your suggestion works and I need to study it. But now the problem is on the caller. Why? – Fred Hors Mar 28 '23 at 22:44
  • @FredHors The code in that question is exactly the same as the code in this question. When I apply my change to your code, [it compiles and runs successfully](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5b63d1ea200614cdce3e0ecff9560926). – cdhowie Mar 28 '23 at 22:54
  • Nope, the code is slighter different, I introduced traits to the question to better reproduce my local issue. – Fred Hors Mar 28 '23 at 22:55
  • @FredHors Okay... when I apply the fix in my answer to that code, [it also works](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e6a104ec30f4db3da31412e0e09cdb8e). It's not clear to me what "the problem is on the caller" means because I cannot reproduce the issue with either version of the code after applying the fix in my answer. – cdhowie Mar 28 '23 at 22:57
  • Sorry it was my fault in reproduction. I managed to reproduce it now: https://stackoverflow.com/questions/75871841/using-async-closure-as-lambda-functions-with-hrtb. – Fred Hors Mar 28 '23 at 23:20
  • Can you please help me understand? – Fred Hors Mar 28 '23 at 23:20
  • are you sill there? – Fred Hors Mar 28 '23 at 23:55