Note that an alternative is to box the returned future (so as to perform dyn
type erasure), since that way what was a hard to express generic parameter becomes a fixed (albeit erased) type, which is less confusing for rust:
+ use ::futures::future::BoxFuture;
impl Doer {
...
- pub async fn call_it<F, Fut>(&mut self, f: F)
+ pub async fn call_it<F >(&mut self, f: F)
where
- F : FnOnce(&'_ mut Doer) -> Fut,
- Fut : Future<Output = ()>,
+ F : FnOnce(&'_ mut Doer) -> BoxFuture<'_, ()>,
{
self.do_it().await;
f(self).await;
self.do_it().await;
}
}
fn main() {
let mut d = Doer {};
block_on(async {
- d.call_it(|a| async move {
+ d.call_it(|a| Box::pin(async move {
a.do_it().await;
a.do_it().await;
- }) .await;
+ })).await;
});
}
Obviously if you can use @steffahn's solution with a good old function rather than a closure, then you should use that one, but for stateful closure the easiest solution is to add this tiny bit of Box
ing (which, by the way, involves the same amount of Box
ing as #[async_trait]
does; so dismissing this heap allocation may be a premature optimization).
-
This solution has also the advantage of showing why your code did not originally work: notice that the returned
BoxFuture<'_, ...>
type does capture the lifetime'_
parameter that matches that of the input&'_ mut Doer
, whereas, in your generic code, the typeFut
is fixed, quantification-wise, before that'_
lifetime is introduced, and thusFut
cannot depend on it. Thus, your signature was requiring a future that did not capture the&'_ mut Doer
parameter.Regarding expressing that property in a generic fahsion, you were looking for:
F : FnOnce(&'_ mut Doer) -> impl '_ + Future<Output = ()>,
i.e.
F : for<'doer> FnOnce(&'doer mut Doer) -> impl 'doer + Future<Output = ()>, // i.e., for<'doer> F : FnOnce(&'doer mut Doer) -> impl 'doer + Future<Output = ()> , // i.e, (pseudo notation) for<'doer> F : FnOnce(&'doer mut Doer) -> X?<'doer> , for<'doer> X?<'doer> : 'doer + Future<Output = ()> , // i.e. (real notation, nightly-only) for<'doer> F : FnOnce<(&'doer mut Doer,)> , for<'doer> <F as FnOnce<(&'doer mut Doer,)>>::Output : 'doer + Future<Output = ()> ,
Sadly the above is not yet fully understood by Rust which gets confused with the higher-order bounds (whilst it may work for explicit
async fn
s, like @steffahn did, it doesn't for closures that involve type inference), but if we replace theimpl
with adyn
in the return type, then the signature becomes, genericity-wise, way simpler, and Rust understands it:F : FnOnce(&'_ mut Doer) -> dyn '_ + Future<Output = ()> // + Send /* for convenience */ // and since a `dyn` cannot be directly returned yet without indirection: F : FnOnce(&'_ mut Doer) -> Pin<Box<dyn '_ + Future<Output = ()> + Send>> // i.e. F : FnOnce(&'_ mut Doer) -> BoxFuture<'_, ()>