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");
}
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:
- async functions return a concrete Type that implements
Future
, therefore the types don't match - 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!