'Pass variable throw closure

I have some async method of structure:

impl ImageBot {
    pub async fn run(&self) {

        let bot_name = self.bot_name.clone();
        let token = self.token.clone();
        let http_host = self.http_host.clone();
        let web_hook_url = self.web_hook_url.clone();

        let bot = teloxide::Bot::new(token).auto_send();

        teloxide::repls2::repl_with_listener(
            bot.clone(),
            |message: Message, bot: AutoSend<teloxide::Bot>| async move {
                let name = bot_name;

                let command_with_args = parse_command(message.text().unwrap(), name);

                if let Some((command, args)) = command_with_args {
                    command_handler(bot, message.clone(), command, args).await
                } else {
                    //
                }

                respond(())
            },
            webhook(bot.clone(), http_host, web_hook_url).await,
        )
        .await;
    }
}

I need to get bot_name for parse_command:

|message: Message, bot: AutoSend<teloxide::Bot>| async move {
                let name = bot_name;

                let command_with_args = parse_command(message.text().unwrap(), name);

Now I get error:
Expected a closure that implements the Fn trait, but this closure only implements FnOnce
on this:
|message: Message, bot: AutoSend<teloxide::Bot>| async move

How can I do this?



Solution 1:[1]

The issue you're hitting is a common one currently due to the lack of async closures, and hence reliance on async move blocks, reformatting a bit:

|message: Message, bot: AutoSend<teloxide::Bot>| {
    async move {
        let name = bot_name;
        [...]

this means bot_name will be moved into the closure, then on the first call it will be moved into the block from the closure.

This means the first call consumes bot_name, and in order to do so has to consume the closure itself, therefore the function can't be called repeatedly.

The solution then is to create a copy inside the closure, which you can then move into the block:

move |message: Message, bot: AutoSend<teloxide::Bot>| {
    let name = bot_name.clone();
    async move {
        [...]
  • the closure is move, this moves bot_name into the closure because since Clone::clone takes a reference otherwise rust will only try to capture a reference, which is unlikely to work out
  • then we create a local name copy, which can be moved inside the block, and the closure doesn't care that it gets dropped during the async process because it still has its bot_name

Solution 2:[2]

The problem is, teloxide::repls2::repl_with_listener expects to get a closure, that can be called multiple times, however, the closure you're passing is consuming the name variable when it's called.

This line is the problem:

let command_with_args = parse_command(message.text().unwrap(), name);

You should rewrite it like so:

let command_with_args = parse_command(message.text().unwrap(), &name);

So that name is only borrowed every time the closure is invoked, instead of beeing consumed

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 gmoshkin