1

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...

3
  • move is not the problem, the problem is that the closure can only be called once because it consumes something that was moved into it. What is the signature of is_user_admin(), is_user_client, etc.? If they take self, modify them to take &self instead. Commented May 12 at 11:22
  • 1
    it takes &self. Problem was in _repo variable, for make closure Fn it have to be cloned in closure as Dizzyi show in his answer Commented May 12 at 23:14
  • Thanks for the followup, I missed that closure was not a move closure, but a closure that executes (and returns) an async move {...} block. The difference matters because with async move, every invocation of the closure moves the value into the future, effectively consuming it (from the closure's point of view) and making it inaccessible for further invocation. That's why the closure implements only the most restrictive FnOnce trait. Commented May 13 at 7:16

1 Answer 1

2

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?

Sign up to request clarification or add additional context in comments.

3 Comments

Thank u a lot for an additional ref. Can u explain one more point, why do u comment filter_command::<AdminCommand>()
oh it's just that I don't have the type definition and don't want to define it, so I comment it out to not show error to myself. It's unrelated to the error anyways. Next time when you asking question, it's better to bring out these unrelated block of code to make your question easier to understand
This answer would be improved with a short explanation of why closures that contain an async move are not Fn, rather than just linking to an external site. Something along the lines of: if the invocation of the closure moves the captured value into the async block it creates, then invocation effectively consumes the value, and makes it inaccessible for future invocations. Since only one invocation is possible, the closure logically only implements FnOnce. Also, the remark about the question should probably be in a comment below the question, not part of the answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.