Lifetime with generic closure parameter returning a future

I have some trouble getting a function that takes a generic closure returning a future right.

A sample for the problem:

struct Test {}

impl Test {
    async fn do_stuff(&self, p: i32) -> i32 {
        return p + 1;
    }
}

async fn call<T, Fut: Future<Output=T>>(f: impl Fn(&mut Test) -> Fut) -> T {
    let mut test = Test {};
    f(&mut test).await
}

async fn run() {
    call(|t| t.do_stuff(123)).await;
}

The return i32 result shouldn't depend on the Test lifetime, but somehow I can't get this to work.

the async function do_stuff() will return an opaque Future type which borrows self, which means the closure

|t| t.do_stuff(123)

also returns some type that borrows t. if rust had higher kinded type, we can express it like this (hypothetical syntax):

async fn call<T, Fut<'_>: Future<Output=T>>(f: impl for <'a> Fn(&'a mut Test) -> Fut<'a>) -> T {
    let mut test = Test {};
    f(&mut test).await
}

currently you can workaround with a trait object Pin<Box<dyn Future<Output=T>+'a>> like this:

async fn call<T>(
	f: impl for<'a> Fn(&'a mut Test) -> Pin<Box<dyn Future<Output = T> + 'a>>,
) -> T {
	let mut test = Test {};
	f(&mut test).await
}
async fn run() {
	call(|t| Box::pin(t.do_stuff(123))).await;
}

or you can use the futures crate:

use futures::future::{BoxFuture, FutureExt};
async fn call<T>(
	f: impl for<'a> Fn(&'a mut Test) -> BoxFuture<'a, T>,
) -> T {
	let mut test = Test {};
	f(&mut test).await
}
async fn run() {
	call(|t| t.do_stuff(123).boxed()).await;
}
1 Like

Is there any way to construct the Boxing inside the method, so that caller is not required to implement boilerplate code?

That's effectively the same as not requiring the type erasure at all, and I don't think there's any good ways to do that yet. There's partial workarounds,[1] but they tend to befuddle Rust's inference, and there aren't always enough knobs to guide closure inference in the face of unnameable, lifetime-capturing return types such as (unboxed) futures.[2] (Sometimes you can replace your closures with functions which have the requisite knobs.)

Brief example that I don't have time to explain in depth right now.

This is similar to this non-async thread, but with the added complication of unnameable types. If you push hard enough, maybe you could get inference to work with the closures.

But it's not going to be less work for callers than adding Box::pin; at best it'd be a macro you provide, I think.


  1. e.g. you can construct bounds which are correct ↩︎

  2. e.g. you can't meet the bounds with a closure because there's no way to convince the compiler to interpret the closure correctly ↩︎

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.