Using async closure as lambda functions is a bit hard

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

>Solution :

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.

Leave a Reply