How to use this argument in this async lambda?

I'm trying the below code but I cannot understand how to fix the error, can you help me?

RUST PLAYGROUND DEMO: Rust Playground

Code:

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

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

#[derive(Debug)]
pub struct Player {
    pub name: String,
}

impl Player {
    pub fn new(name: &str, team_id: i64) -> Result<Player, String> {
        if team_id > 100 {
            return Err("Sorry, this team is closed".to_string());
        }

        Ok(Player {
            name: name.to_string(),
        })
    }
}

pub struct PlayerInput {
    pub name: String,
}

pub struct PlayerLambdaArgs {
    pub random_team_id: i64,
}

pub struct DBRepo {
    pub db: String,
}

impl DBRepo {
    pub fn new(db: String) -> Self {
        Self { db }
    }
}

#[async_trait::async_trait]
impl PlayerTrait for DBRepo {
    async fn player_update<'a>(
        &'a self,
        _input: PlayerInput,
        lambda: &(dyn Fn(
            PlayerLambdaArgs,
        ) -> Pin<Box<dyn Future<Output = Result<Player, String>> + Send + 'a>>
              + Sync),
    ) -> Result<Player, String> {
        // get from DB here
        let random_team_id = 123;

        let lambda_args = PlayerLambdaArgs { random_team_id };

        let res = lambda(lambda_args).await?;

        // do something with res here
        Ok(Player { name: res.name })
    }
}

#[tokio::main]
async fn main() {
    let db_repo = Arc::new(DBRepo::new(String::from("hello")));

    let input = PlayerInput {
        name: "Bob".to_string(),
    };

    let player = db_repo
        .player_update(input, &|args| {
            Box::pin(async {
                let player = Player::new(&input.name, args.random_team_id)?;

                Ok(player)
            })
        })
        .await;

    dbg!(player);
}

Error:

Compiling playground v0.0.1 (/playground)
error[E0373]: async block may outlive the current function, but it borrows `args.random_team_id`, which is owned by the current function
  --> src/main.rs:82:28
   |
82 |               Box::pin(async {
   |  ____________________________^
83 | |                 let player = Player::new(&input.name, args.random_team_id)?;
   | |                                                       ------------------- `args.random_team_id` is borrowed here
84 | |
85 | |                 Ok(player)
86 | |             })
   | |_____________^ may outlive borrowed value `args.random_team_id`
   |
note: async block is returned here
  --> src/main.rs:82:13
   |
82 | /             Box::pin(async {
83 | |                 let player = Player::new(&input.name, args.random_team_id)?;
84 | |
85 | |                 Ok(player)
86 | |             })
   | |______________^
help: to force the async block to take ownership of `args.random_team_id` (and any other referenced variables), use the `move` keyword
   |
82 |             Box::pin(async move {
   |                            ++++

For more information about this error, try `rustc --explain E0373`.
error: could not compile `playground` due to previous error

The first thing I did was apply the help hint to get rid of the borrow of args.random_team_id.

Playground.

But now we're moving too much into the closure, so instead I pre-borrowed &input.name in a local variable -- this way we don't move input.name itself, we move the borrowing variable.

+    let input_name = &input.name;
     let player = db_repo
         .player_update(&input, &|args| {
             Box::pin(async move {
-                let player = Player::new(&input.name, args.random_team_id)?;
+                let player = Player::new(input_name, args.random_team_id)?;

Playground.

Thank you very much.

I understood the move fix.

The real issue now is with input: in my real code it contains a lot of fields, what do you suggest?

Wait, I think I messed that up :sweat_smile:

OK, it's fine, I just confused myself for a second.

Here's a better suggestion than my first suggestion that covers your follow-up question.

+     // diff is from your original playground
      let player = db_repo
         .player_update(&input, &|args| {
+            let input = &input;
-            Box::pin(async {
+            Box::pin(async move {
                let player = Player::new(&input.name, args.random_team_id)?;

This moves a borrow of all of input instead of one field.

1 Like

Ok. Thanks.

What do you think about this pattern? Isn't a common/idiomatic way in Rust?

I need it because in the closure I'm passing arguments from repository pattern, maybe in a DB transaction...

The wider pattern, I don't know, maybe that deserves a follow-up post. I haven't had to deal with much async so others may have more to offer.

The general pattern of

  • Oops, need to make my closure move to capture stuff
  • Oops, now I'm capturing other stuff I only want to borrow
  • Create explicit borrowing variables and capture those instead

Is not uncommon, since move is an all or nothing annotation.

1 Like