asynchronousrustclosurestelegram-botrust-tokio

dptree::filter_async problem with move semantic


I'm writing a bot on Rust via teloxide + tokio. I have this run method (called from main)

pub async fn run() {
    dotenv::dotenv().ok();
    pretty_env_logger::init();
    log::info!("Starting command bot...");

    let bot = Bot::from_env();
    let pool = PgPool::connect(&dotenv::var("DATABASE_URL").unwrap())
        .await
        .unwrap();

    let admin_repo = AdminRepository::new(pool.clone());
    let worker_repo = WorkerRepository::new(pool.clone());
    let client_repo = ClientRepository::new(pool.clone());
    let handler = Update::filter_message()
        .branch(
            dptree::entry()
                .filter_command::<BaseCommand>()
                .endpoint(answer_base_command),
        )
        .branch(
            dptree::filter_async(|msg: Message| async move {
                match msg.from {
                    None => false,
                    Some(user) => admin_repo.is_user_admin(user.id.0).await,
                }
            })
            .filter_command::<AdminCommand>()
            .endpoint(answer_admin_command),
        )
        .branch(
            dptree::filter_async(|msg: Message| async move {
                match msg.from {
                    None => false,
                    Some(user) => worker_repo.is_user_worker(user.id.0).await,
                }
            })
            .filter_command::<WorkerCommand>()
            .endpoint(answer_worker_command),
        )
        .branch(
            dptree::filter_async(|msg: Message| async move {
                match msg.from {
                    None => false,
                    Some(user) => client_repo.is_user_client(user.id.0).await,
                }
            })
            .filter_command::<ClientCommand>()
            .endpoint(answer_client_command),
        );
    let bot_state = BotState {
        user_repo: UserRepository::new(pool.clone()),
        worker_repo: WorkerRepository::new(pool.clone()),
        admin_repo: AdminRepository::new(pool.clone()),
        client_repo: ClientRepository::new(pool.clone()),
    };

    Dispatcher::builder(bot, handler)
        .dependencies(dptree::deps![bot_state])
        .default_handler(|upd| async move {
            log::warn!("Unhandled update: {:?}", upd);
        })
        .error_handler(LoggingErrorHandler::with_custom_text("An error"))
        .enable_ctrlc_handler()
        .build()
        .dispatch()
        .await;
}

I have this problev while cargo check for each branch with async_filter

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
  --> src/lib.rs:47:34
   |
47 |               dptree::filter_async(|msg: Message| async move {
   |               -------------------- -^^^^^^^^^^^^^
   |               |                    |
   |  _____________|____________________this closure implements `FnOnce`, not `Fn`
   | |             |
   | |             required by a bound introduced by this call
48 | |                 match msg.from {
49 | |                     None => false,
50 | |                     Some(user) => admin_repo.is_user_admin(user.id.0).await,
   | |                                   ---------- closure is `FnOnce` because it moves the variable `admin_repo` out of its environment
51 | |                 }
52 | |             })
   | |_____________- the requirement to implement `Fn` derives from here
   |
   = note: required for `{closure@src/lib.rs:47:34: 47:48}` to implement `Injectable<DependencyMap, bool, (teloxide::prelude::Message,)>`
note: required by a bound in `teloxide::dptree::filter_async`
  --> /home/skyman/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/dptree-0.3.0/src/handler/filter.rs:35:11
   |
31 | pub fn filter_async<'a, Pred, Input, Output, FnArgs, Descr>(
   |        ------------ required by a bound in this function
...
35 |     Pred: Injectable<Input, bool, FnArgs> + Send + Sync + 'a,
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `filter_async`


I tried to remove "move" (just one branch for example)

.branch(
            dptree::filter_async(|msg: Message| async {
                match msg.from {
                    None => false,
                    Some(user) => admin_repo.is_user_admin(user.id.0).await,
                }
            })
            .filter_command::<AdminCommand>()
            .endpoint(answer_admin_command),

but getting error with lifetimes

 |             dptree::filter_async(|msg: Message| async {
   |                                  ^^^^^^^^^^^^^^ may outlive borrowed value `admin_repo`
...
50 |                     Some(user) => admin_repo.is_user_admin(user.id.0).await,
   |                                   ---------- `admin_repo` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src/lib.rs:47:13
   |
47 | /             dptree::filter_async(|msg: Message| async {
48 | |                 match msg.from {
49 | |                     None => false,
50 | |                     Some(user) => admin_repo.is_user_admin(user.id.0).await,
51 | |                 }
52 | |             })
   | |______________^
help: to force the closure to take ownership of `admin_repo` (and any other referenced variables), use the `move` keyword
   |
47 |             dptree::filter_async(move |msg: Message| async {
   |                                  ++++


if i do changes from cargo, i get another error with lifetime

error: lifetime may not live long enough
  --> src/lib.rs:67:54
   |
67 |               dptree::filter_async(move |msg: Message| async {
   |  __________________________________-------------------_^
   | |                                  |                 |
   | |                                  |                 return type of closure `{async block@src/lib.rs:67:54: 67:59}` contains a lifetime `'2`
   | |                                  lifetime `'1` represents this closure's body
68 | |                 match msg.from {
69 | |                     None => false,
70 | |                     Some(user) => client_repo.is_user_client(user.id.0).await,
71 | |                 }
72 | |             })
   | |_____________^ returning this value requires that `'1` must outlive `'2`
   |
   = note: closure implements `Fn`, so references to captured variables can't escape the closure

How should I change async closure to make it work? I have a feeling that it should be easier then I trying to do...

Also I would be happy for another code-improvements


Solution

  • try this

    let admin_filter = move |msg: Message| {
        // clone the variable from the environment to capture it
        let admin_repo = admin_repo.clone();
        async move {
            match msg.from {
                None => false,
                Some(user) => admin_repo.is_user_admin(user.id.0).await,
            }
        }
    };
    
    let handler = teloxide::types::Update::filter_message().branch(
        dptree::filter_async(admin_filter)
            // .filter_command::<AdminCommand>()
            .endpoint(answer_admin_command),
    );
    

    Ref:

    Why don’t async move closures implement Fn?

    Remark:

    your question is very complex, try see Minimal, Reproducible Example, to learn to write cleaner question.