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?