Having trouble on making async callable wrapper

Hello. I am trying to make a wrapper struct of "async callable" objects because I am making a trait that requires to implement a method that returns async closures and I want that trait to be object-safe.

I will provide the minimal code example I tried in Rust Playground Gist=c5d2e0...

The code of AsyncCallable is inspired by rust-lang/rust#93582.

The reason why I added Fut: ?Sized on AsyncCallableWrapper is because compiler suggested me to relaxing implicit Sized bound of it.

I thought that async closure implements Fn(i32) -> impl Future<Output = i32> therefore it automatically implements AsyncCallable<i32, Future=..., Out=i32, Output=...>. But seems I am wrong, according to the compilation result.

Also there is another compilation error about unknown size of (dyn Future<Output = i32> + 'static), but I thought that's return type of incremented which I think would be covered by AsyncCallableWrapper::sender and I don't know why the returning type in Box being unknown is a problem.

What is a problem of the above code? How can I fix it to achieve my original goal?

Thanks in advance.

an async block is itself a struct implementing Future, it is not a function (or closure) that returns a Future. if you want a closure that returns a future, you can simply make a closure that returns an async block. keep in mind this is different from the async fn syntax sugar, mainly due to capturing rules, example:

// f is a closure that returns a future
let f = |arg| async move {
    foo_bar(arg).await
};

I didn't look into the code thoroughly, but from your description, your trait looks very much like the async-fn-traits crate, maybe you can take some inspiration from that crate:

I am sorry, I think I confused between closure that returns an async block and async block. My code is actually covering closure that returns an async block.

I edited my original comment. Not sure if I can call that as async closure, but I used async closure for it now.

I don't know what problem are you really trying to solve. both the Future trait and Fn trait are already object-safe. it sounds like there's some misconception for you, but I'm not sure what it is.

I think you misunderstood the compile error somehow. the compiler complains that dyn Future<Output=...> isn't Sized, it indicates you are not allowed to use a trait object, it's not saying you should relax the trait bounds on the type definition. in your example:

struct AsyncCallableWrapper<'se, In, Fut, Out>
where
    Fut: Future<Output = Out> + ?Sized,
{
    pub sender: Box<&'se dyn AsyncCallable<In, Future = Fut, Out = Out, Output = Fut>>,
}

the generic type Fut is the Future associated type of AsyncCallable, and AsyncCallable::Future is the return type of Fn(In) -> Future super trait, and function return type must be Sized, so the ?Sized bound is meaningless anyway.

the closure type (and so the future type) cannot be named, neverthless, it is a concrete type, and not a (unsized) trait object. as I stated above, the return type of Fn (i.e. the Output associated type of the super trait FnOnce) cannot be unsized. you either use generic and let the compiler infer the type, or you use a boxed trait object.

also, in your code, sender has type Box<&'se dyn AsyncCallable<...>>, which is meaningless, either use a borrowed trait object, or use an owned trait object.

again, your code makes absolute no sense to me, but here it is how I modified it to compile anyway:

1 Like

No, the trait with method returning impl Future or impl Fn won't be object-safe, because impl Trait is an opaque type. Also you cannot make type hint of impl Fn(...) -> impl Future<Output = T>, because using impl as return type of Fn is not supported yet.

The problem I want to solve is that I want to provide something like SomeTrait in below code;

trait SomeTrait {
    fn some_async_callable(...) -> SomeAsyncCallable;
}

fn get_list_of_protocols(...) -> Vec<dyn SomeTrait> {
    todo!()
}

I will play with your compilable code and reply if there is any concern or question.

such traits are never object safe, and I don't see how the AsyncCallable trait could help: impl AsyncCallable is no different than impl Fn().

if you think you can return Box<dyn AsyncCallable>, then you can also return Box<dyn Fn(...)> just fine.

you can just use the Fn trait for SomeAsyncCallable, as Fn is already object-safe:

trait SomeTrait {
    fn some_async_callable(...) -> Box<dyn Fn(i32) -> Box<dyn Future<Output=i32>>;
}

the return type Vec<dyn SomeTrait> is invalid, owned trait objects should be boxed up (Vec<Box<dyn SomeTrait>>), or you can return borrowed trait objects (Vec<&dyn SomeTrait>):

again, I feel like you are solving a problem with the wrong tools, but my guess is without any context and not based anyway.

I made following compilable code from your advice;

use std::future::Future;

trait SomeTrait {
    fn some_async_callable() -> Box<dyn Fn(i32) -> Box<dyn Future<Output=i32>>>;
}

struct SomeStruct {}

impl SomeTrait for SomeStruct {
    fn some_async_callable() -> Box<dyn Fn(i32) -> Box<dyn Future<Output=i32>>> {
        Box::new(|i: i32| Box::new(async move {i+1}))
    }
}

fn main() {}

So basically I wanted to hold different kind of "set of async I/O functionalities" under same container. So I made Protocol trait which returns async closure(= closure that returns async block) that does networking, and made several structs implementing that Protocol. Some struct is doing standard HTTPS requests, and some structs are connecting to message queues like Redis etc, under same interface.

Following code is a very rough example of my application.

async fn connect(protocol: Box<dyn Protocol>, message: String) -> String {
    let closure_returns_future = protocol.send_order();
    let response_message: String = closure_returns_future(&message).await;
    response_message
}

However I failed to find the way to give a type hint on trait methods directly returning async closure. You cannot use impl Fn(...) neither dyn Fn(...) -> impl Future<...> as return type of trait method. And then I started to make a wrapper struct of dyn Fn(...) -> impl Future<...> to make this possible.

That was my original concern. I hope you can understand my problem and my past approach.

Your code works with boxing every closure and each closure's returning async block. If I fail to find elegant way to solve my original concern, I will use this way.

your design feels very much like nodejs to me. in a dynamically (and weakly) typed language, this design is all well and good, but it feels non idiomatic in rust, which is why you find yourself in a position fighting the type systemsall the time (have to erase types at all levels, and maybe many lifetime related problems too, I would guess?)

if your goal is to be able to re-configure the behavior at runtime, I would suggest a more coarse-grained design, where you erase the types only for some high-level "Actor" or "Strategy" traits, while you can implement each individual instance using regular async code.

if it is unavoidable to do fine grained concurrency, I'd suggest you use an async task scheudling library, such as async-executor. (note the term "executor" only refers to the scheduling functionality, which is sligthly different from the same term used by tokio) although async-executor comes as part of the smol async ecosystem, it is fully compatible with other async runtimes too.

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.