How to call struct method from async closure?

What I want to do?:

  • I want to send an HTTP request using the value of the Websocket response.(Examples are simplified.)
  • making market making bot(crypto).
  • If possible, I'd like to aggregate abstracted methods in the client.

What are you having trouble with?:

  • Can I call struct method from closure?
  • Or, Is the idea wrong in the first place?

Example:

use anyhow::Result;
use async_trait::async_trait;
use core::future::Future;

#[async_trait]
trait Client<'a> {
    async fn new(s: &'a str) -> Self;
    async fn callback<C, F>(&mut self, mut cb: C) -> Result<()>
    where
        C: FnMut(String) -> F + Send,
        F: Future<Output = ()> + Send;
    async fn hello(&self) -> Result<()>;
}

#[derive(Debug)]
struct A<'a> {
    a: &'a str,
}

#[async_trait]
impl<'a> Client<'a> for A<'a> {
    async fn new(a: &'a str) -> Self {
        Self { a }
    }
    async fn callback<C, F>(&mut self, mut cb: C) -> Result<()>
    where
        C: FnMut(String) -> F + Send,
        F: Future<Output = ()> + Send,
    {
        cb("callback".to_string()).await;
        Ok(())
    }
    async fn hello(&self) -> Result<()> {
        println!("\nhello from same struct method:\n{}", "hello world");
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    let mut client: A = Client::new("hello").await;
    println!("\nclient struct is:\n{:?}", client);

    client
        .callback(|res| async move {
            println!("\ncallback response is:\n{:?}", res);

            // TODO: How to call method as "client" struct?
            client.hello().await;
        })
        .await;

    Ok(())
}

Error:

error[E0507]: cannot move out of `client`, a captured variable in an `FnMut` closure
  --> src/main.rs:45:25
   |
41 |       let mut client: A = Client::new("hello").await;
   |           ---------- captured outer variable
...
45 |           .callback(|res| async move {
   |  ___________________-----_^
   | |                   |
   | |                   captured by this `FnMut` closure
46 | |             println!("\ncallback response is:\n{:?}", res);
47 | |
48 | |             // TODO: How to call method as "client" struct?
49 | |             client.hello().await;
   | |             ------
   | |             |
   | |             variable moved due to use in generator
   | |             move occurs because `client` has type `A<'_>`, which does not implement the `Copy` trait
50 | |         })
   | |_________^ move out of `client` occurs here

error[E0505]: cannot move out of `client` because it is borrowed
  --> src/main.rs:45:19
   |
44 | /     client
45 | |         .callback(|res| async move {
   | |                   ^^^^^ move out of `client` occurs here
46 | |             println!("\ncallback response is:\n{:?}", res);
47 | |
48 | |             // TODO: How to call method as "client" struct?
49 | |             client.hello().await;
   | |             ------ move occurs due to use in closure
50 | |         })
   | |__________- borrow of `client` occurs here

Rust has exclusive ownership, and moves preserve it. async move { client } means the one and only instance of the client is moved to inside that block, and exists only inside it, and no other place in the code is allowed to touch it.

The second problem is that &mut self is also exclusive. It says that at the moment of calling this method, &mut self is 100% guaranteed to be the only way to access the client, and nothing else anywhere can touch it. But that's not true if you try to put the client also in cb.

You must relax the exclusivity here.

Using Arc<Client> will let you have a copy of the client inside and outside of the async block.

Using &self methods will let you access the client while there's some other way to access it elsewhere too. You may need Mutex if you also need to mutate in &self methods.

Alternatively, split the code into two independent structs - one for callback, another for hello, so that each can have the exclusivity without blocking the other.

Also note the lifetime on the trait is very suspicious. struct A<'a> is probably a design error, unless you mean it to be a temporary scope-bound view that does not store data in a, but references a local variable instead, and can't be used outside of the function where that variable exists. 99% of the time that doesn't make sense, and it should be a String instead, and you won't need lifetimes on the struct or the trait.

1 Like

This might be the best way for me.

Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.