1

I have a situation where there is a service manager (struct B) holding a number services (struct A) that need to call a method in the service manager (e.g. self.test with self in B) , all working deeply nested in async tokio code. While I am able to "attach" a clone of the service manager to each service to be able to call its methods, I would like to allow access only to specific async(!) methods in the service manager, i.e. I would like to only attach for instance one method of the service manager to a service. (For those interested in the real system, some services call methods in other services and get them through a call in the service manager, I know there are things like channels, pub/sub etc, but I am interested in a solution to this specific setup because I com across it often...) I am unfortunately not able to find the correct way how to set such a callback. Below is a very simplified version of the whole setup:

#![feature(async_closure)]

use std::sync::Arc;

type AsyncClosure = ???;

struct A {
    value: String,
    callback_to_fn_in_B: Option<AsyncClosure>,
}

impl A {
    fn set_callback_to_method_in_B(&mut self, f: Option<AsyncClosure>) {
        self.callback_to_fn_in_B = f;
    }
}

struct B {
    b: String,
    a: Arc<A>,
}

impl B {
    async fn test(&self, arg0: &str) -> String {
        // to simulate some async work:
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        format!("b + arg0 = {}{}", self.b, arg0)
    }

    fn attach_self_callback_to_A(&self) {
        let callback = async move |arg: &str| {
            self.test(arg).await
        };
        self.a.set_callback_to_method_in_B(Some(callback));
    }
}
#[tokio::main]
async fn main() {
    tokio::spawn(async move {
        let a = Arc::new(A { value: "value".to_owned(), callback_to_fn_in_B: None });
        let b = B { b: "hello".to_owned(), a: a.clone()  };
        b.attach_self_callback_to_A();
        match a.callback_to_fn_in_B.as_ref() {
            Some(f) => println!("{:?}", f(" world")),
            None => {},
        }
        
    });
}

I left tokio etc. in there to show that the whole system should work across threads, i.e. some parts may require + Send + Sync. How do I define the type AsyncClosure? Is it possible to avoid a closure (I would use it to capture self of the main structure B)? Any help is greatly appreciated!

Base on the first answer below, I move to the following version:

#![feature(async_closure)]
#![allow(unused_imports)]

use std::sync::Arc;
use std::error::Error;
use futures::future::Future;
use std::pin::Pin;

type AsyncClosure = Box<dyn for<'a> FnMut(&'a str) -> Pin<Box<dyn Future<Output = String> + 'a + Send + Sync>> + Send + Sync>;
struct A
{
    value: String,
    callback_to_fn_in_B: Option<AsyncClosure>,
}

impl A
{    
    fn set_callback_to_method_in_B(&mut self, f: Option<AsyncClosure>) {
        self.callback_to_fn_in_B = f;
    }
}

struct B
{
    b: String,
    a: Arc<A>,
}

impl B
{
    async fn test(&self, arg0: &str) -> String {
        // to simulate some async work:
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        format!("b + arg0 = {}{}", self.b, arg0)
    }

    async fn attach_self_callback_to_A(&self) {
        let this = self.clone();
        let callback: AsyncClosure = Box::new(|arg| { Box::pin(async move { this.test(arg).await })});
        self.a.set_callback_to_method_in_B(Some(callback));
    }
}

#[tokio::main]
async fn main() {
    tokio::spawn(async move {
        let a = Arc::new(A { value: "value".to_owned(), callback_to_fn_in_B: None });
        let b = B { b: "hello".to_owned(), a: a.clone()  };
        b.attach_self_callback_to_A();
        match a.callback_to_fn_in_B.as_ref() {
            Some(f) => {
               println!("{:?}", f("test").await)
            },
            None => {},
        }
        
    });
}

However, now the compiler complains that it cannot infer an appropriate lifetime due to conflicting requirements for the &self in the fn attach_self_callback_to A. The error message is quite long and mostly repeats itself without giving me any real help. Anyone any idea how to solve this?

Daniel Schaadt
  • 99
  • 1
  • 10
  • Does this answer your question? [How can I store an async function in a struct and call it from a struct instance?](https://stackoverflow.com/questions/58173711/how-can-i-store-an-async-function-in-a-struct-and-call-it-from-a-struct-instance) – Ibraheem Ahmed Jan 28 '21 at 16:52
  • Unfortunately, not. When I try that, I get the following error: mismatched types expected type parameter `C` found closure `[closure@... every closure has a distinct type and so could not always match the caller-chosen type of parameter `C`rustcE0 Also, it would complicate the whole code to to the marking everywhere. A trait object solution would probably be easier. – Daniel Schaadt Jan 28 '21 at 17:19

0 Answers0