Async closure accepting another async closure

I have some tests, that all look similar:

#[test]
fn some_test() {
    // create system under test
    let sut = SomeService {};
    // do some setup
    sut.setup();
    let result = sut.some_op("foo".to_string(), 42);
    assert_eq!(true, result);
}

I want to reduce boilerplate, so I would like to create a helper/wrapper, that I will use like this:

#[test]
fn some_test() {
    test_helper(|operation| {
        let result = operation("foo".to_string(), 42);
        assert_eq!(true, result);
    });
}

To do so, I need to create a fuction accepting closure, which itself also accepts closure.
I've struggled a bit with it, until i discovered this post.

So my implementation now looks like this and works:

pub fn test_helper<T>(test_body: T) -> ()
where
    for<'a> T: FnOnce(&'a dyn Fn(String, i32) -> bool) -> (),
{
    let sut = SomeService {};
    sut.setup();

    test_body(&|s, i| sut.some_op(s, i))
}

#[test]
fn some_test() {
    test_helper(|operation| {
        let result = operation("foo".to_string(), 42);
        assert_eq!(true, result);
    });
}

But now the real problem - how do I make it all async?

I tried to do this using generics and constraints:

pub async fn test_helper<T, Fut1, Fut2>(test_body: T) -> ()
where
    for<'a> T: FnOnce(&'a dyn Fn(String, i32) -> Fut1) -> Fut2,
    Fut1: Future<Output = bool>,
    Fut2: Future<Output = ()>,
{
    let sut = SomeService {};
    sut.setup();

    test_body(&|s, i| -> Fut1 { sut.some_op_async(s, i) }).await;
}

It does not compile with error

error[E0308]: mismatched types
  --> src/main.rs:55:37
   |
11 |     async fn some_op_async(&self, a1: String, a2: i32) -> bool {
   |                                                           ---- the `Output` of this `async fn`'s found opaque type
...
46 |     pub async fn test_helper<T, Fut1, Fut2>(test_body: T) -> ()
   |                                 ---- this type parameter
...
55 |         test_body(&|s, i| -> Fut1 { sut.some_op_async(s, i) }).await;
   |                                     ^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `Fut1`, found opaque type
   |
   = note: expected type parameter `Fut1`
                 found opaque type `impl futures::Future`
   = help: type parameters must be constrained to match other types

If I understand Rust generics correctly, it means that type parameter Fut1 will be specified by the caller, and it may very well not match the concrete implementation that I use inside my function.

I also randomly tried using trait objects, Box'es, and other stuff that I've seen during my short experience with Rust.

Can you please help me fix my code?

Try this:

use std::future::Future;
use futures::future::BoxFuture;

type BoxedFn = Box<dyn FnOnce(String, i32) -> BoxFuture<'static, ()>>;

struct SomeService {}
impl SomeService {
    fn setup(&self) {}
    async fn some_op_async(&self, s: String, i: i32) {}
}

pub async fn test_helper<F, Fut>(test_body: F) -> ()
where
    F: FnOnce(BoxedFn) -> Fut,
    Fut: Future<Output = bool>,
{
    let sut = SomeService {};
    sut.setup();

    test_body(Box::new(|s, i| Box::pin(async move {
        sut.some_op_async(s, i).await
    }))).await;
}
1 Like

First of all, thanks alot.

This is much closer to the truth than anything I've done, but it does not compile with the following error:

error[E0271]: type mismatch resolving `<impl futures::Future as futures::Future>::Output == ()`
  --> src/main.rs:84:13
   |
84 |             Box::pin(async move { sut.some_op_async(s, i).await })
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `bool`
   |
   = note: required for the cast to the object type `dyn futures::Future<Output = ()> + std::marker::Send`

I managed to work it out by myself - the problem was in type BoxedFn having return type of (), when it actually returns bool.

Now this works:

type BoxedFn = Box<dyn FnOnce(String, i32) -> BoxFuture<'static, bool>>;

pub async fn test_helper<F, Fut>(test_body: F) -> ()
where
    F: FnOnce(BoxedFn) -> Fut,
    Fut: Future<Output = bool>,
{
    let sut = SomeService {};
    sut.setup();

    test_body(Box::new(|s, i| {
        Box::pin(async move { sut.some_op_async(s, i).await })
    }))
    .await;
}

Thank you, @alice, for helping!

You're welcome!