Store multiple closures in a vec

Hello there,
I can't figure out the correct lifetime annotations for my code.

My goal is to have a vector of Tests, where each test has a name and a function. Each test function takes two parameters: A reference with static lifetime, and a mutable reference which can only be used during the invocation of the function.

The test functions are async internally, but the external interface can just as well be sync. Everything worked well when the test functions would only take a single static parameter. But as soon as I added the second parameter, I run into the error.

How do I need to change my code, that it compiles again?

Thank you

use core::fmt::Debug;
use core::future::Future;
use futures::executor::block_on;

struct StaticParam;
struct Param;

struct Test<F> {
    name: &'static str,
    fun: F,
}

impl<F> Test<F> {
    const fn new(name: &'static str, fun: F) -> Self {
        Self { name, fun }
    }
}

trait Testable {
    fn name(&self) -> &'static str;
    fn run(&self, st: &'static StaticParam, nsta: &mut Param) -> Result<String, String>;
}

impl<Fut, F, S: Debug, E: Debug> Testable for Test<F>
where
    for<'a> F: Fn(&'static StaticParam, &'a mut Param) -> Fut + 'a,
    Fut: Future<Output = Result<S, E>>,
{
    fn name(&self) -> &'static str {
        self.name
    }
    fn run(&self, st: &'static StaticParam, nsta: &mut Param) -> Result<String, String> {
        match block_on((self.fun)(st, nsta)) {
            Ok(p) => {
                Ok(format!("{:?}", p))
            }
            Err(e) => {
                Err(format!("{:?}", e))
            }
        }
    }
}

const TESTS: &[&dyn Testable] = &[
    &Test::new("i2c", test_i2c),
    &Test::new("co2", test_co2),
];

async fn test_i2c(_sta: &'static StaticParam, _nsta: &mut Param) -> Result<(), ()> {
    todo!()
}

async fn test_co2(_sta: &'static StaticParam, _nsta: &mut Param) -> Result<(), ()> {
    todo!()
}


static STA: StaticParam = StaticParam;

fn main() -> ! {
    
    let mut nsta = Param;
     for test in TESTS {
         test.run(&STA, &mut nsta);
    }
    loop {}
    
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/main.rs:45:5
   |
45 |     &Test::new("i2c", test_i2c),
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected opaque type `impl for<'a> futures::Future<Output = Result<(), ()>>`
              found opaque type `impl futures::Future<Output = Result<(), ()>>`
   = help: consider `await`ing on both `Future`s
   = note: distinct uses of `impl Trait` result in different opaque types

error[E0308]: mismatched types
  --> src/main.rs:46:5
   |
46 |     &Test::new("co2", test_co2),
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected opaque type `impl for<'a> futures::Future<Output = Result<(), ()>>`
              found opaque type `impl futures::Future<Output = Result<(), ()>>`
   = help: consider `await`ing on both `Future`s
   = note: distinct uses of `impl Trait` result in different opaque types

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` (bin "playground") due to 2 previous errors

&Trait needs to be coerced to &dyn Trait, and this doesn't automatically happen in arrays/slices. Try adding as _ or as &dyn Testable after the first element.

1 Like

Hmm.

const TESTS: &[&dyn Testable] = &[
    &Test::new("i2c", test_i2c) as &dyn Testable,
    &Test::new("co2", test_co2) as &dyn Testable,
];

yields the same error

I think the issue is that the Testable impl requires for<'a> F: Fn(&'static StaticParam, &'a mut Param) -> Fut + 'a, while the test_i2c and test_co2 values are only for a specific 'a rather than for all possible 'a.

This can't work, because you're wanting to allow Fut to borrow 'a, but 'a isn't (and can't be) in scope for that; Fut is actually a family of types that needs to be parameterized by 'a, but you've declared it as a single type parameter impl<Fut, ....

What you need to do is use a trait alias which transforms the function return type into an associated type, so you don't have to declare it as a type parameter. One is already available on crates.io as async_fn_traits::AsyncFn2, which also makes it easy to specify the future's output type:

impl<F, S: Debug, E: Debug> Testable for Test<F>
where
    for<'a> F: AsyncFn2<&'static StaticParam, &'a mut Param, Output=Result<S, E>>,
2 Likes

Nice. Thank you!

Modified and working gist: