Higher-Rank Trait Bounds use bound lifetime in another generic

I want to register a callback that is async. To test if this is feasible/possible in Rust I've played around with this piece of code:

use std::future::Future;
use std::pin::Pin;

struct X {
    chain: Vec<Box<dyn for<'a> FnOnce(&'a mut Self) -> Pin<Box<dyn Future<Output = ()> + 'a>>>>,
}

impl X {
    fn new() -> X {
        X {
            chain: Default::default(),
        }
    }

    fn add<F, R>(&mut self, f: F)
    where
        F: 'static + for<'a> FnOnce(&'a mut Self) -> R,
        R: Future<Output = ()> + 'a,
    {
        self.chain.push(Box::new(|x| Box::pin(f(x))));
    }

    async fn run(&mut self) {
        while let Some(x) = self.chain.pop() {
            x(self).await;
        }
    }
}

async fn test(_: &mut X) {
    println!("b");
}

fn main() {
    let mut x = X::new();

    x.add(test);
    println!("a");
    futures::executor::block_on(x.run());
    println!("c");
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0261]: use of undeclared lifetime name `'a`
  --> src/main.rs:18:34
   |
18 |         R: Future<Output = ()> + 'a,
   |                                  ^^ undeclared lifetime
   |
help: consider introducing lifetime `'a` here
   |
8  | impl<'a> X {
   |     ^^^^
help: consider introducing lifetime `'a` here
   |
15 |     fn add<'a, F, R>(&mut self, f: F)
   |            ^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0261`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

Obviously the 'a lifetime specified on F in add<F, R> does only apply to the FnOnce. The Function would normaly look like this: for<'a> FnOnce(&'a mut Self) -> (dyn Future<Output=()> + 'a). But I need a concrete type as the return type because:

  1. async functions return a concrete Type that implements Future, therefore the types don't match
  2. To call await on it, I need either a concrete type or a Pin<Box<dyn Future>>

I could rewrite add like this:

fn add<F: 'static + for<'a> FnOnce(&'a mut Self) -> Pin<Box<dyn Future<Output=()> + 'a>>>(&mut self, f: F) {
    self.chain.push(Box::new(f));
}

This works, however I'd need a wrapper closure of |x| Box::pin(f(x)) at every add call location, which is not so nice. I'd prefer to simply just use x.add(f) instead of x.add(|x| Box::pin(f(x)))

If I take one of the compiler suggestions, the lifetime 'a becomes bound to the function, which is not the same.

So I'm looking for a way to use the lifetime 'a as defined on F in the generic type R, so that the type is correct.

I haven't found the syntax for this, nor do I know if this is even possible in the type system.

Suggestions are highly appreciated! Thanks!

Here’s a way to do it.

2 Likes

You may also be interested in this post: Accepting an async closure + lifetimes = stumped - #8 by Yandros

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.