Lifetimes for a boxed async fn that borrows an argument

I have some async fn's that I'd like to add to a collection (which requires me to wrap them in a Box). I'd then like to call these functions with borrowed data that does not outlive the collection.

use futures::future::BoxFuture;


// some async fns I want to refer to as items of a Vec
async fn product(numbers: &[u64]) -> u64 { numbers.iter().product() }
async fn sum(numbers: &[u64]) -> u64 { numbers.iter().product() }


// a wrapper around the async fns so they have the same type
fn wrap<'a, H: 'static, F: 'a>(h: H) ->
    Box<dyn Fn(&'a [u64]) -> BoxFuture<'a, u64>>
where
    H: Fn(&'a [u64]) -> F,
    F: std::future::Future<Output = u64> + Send,
{
    Box::new(move |n| Box::pin(h(n)))
}


// THIS WORKS
// I can now refer to the async fns in a Vec
async fn example1() {
    let numbers = vec![1, 2, 3];
    let handlers = vec![wrap(product), wrap(sum)];
    handlers[0](&numbers).await;
}


// THIS DOESN'T
// what if I want handlers to outlive numbers?
// what should the lifetimes be?
async fn example2() {
    let handlers = vec![wrap(product), wrap(sum)];
    {
        let numbers = vec![1, 2, 3];
        handlers[0](&numbers).await;
    }
}


// THIS WORKS
// without the async fn wrapper it compiles
async fn example3() {
    let handlers = vec![product];
    {
        let numbers = vec![1, 2, 3];
        handlers[0](&numbers).await;
    }
}

Link to above code in playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=40c126aa3f530ff38198f0e4dc9cda9b

Sorry, the lifetime has to be defined in the Box and not on wrap. The type you want is

Box<dyn for<'a> Fn(&'a [u64]) -> BoxFuture<'a, u64>>

But this is not possible because then you can no longer talk about 'a on the type F. Unfortunately it is not possible to define a wrap function that does this due to limitations in the compiler. One option would be to define a macro:

macro_rules! wrap {
    ($closure:expr) => {{
        #[allow(unused_mut)]
        let mut closure = $closure;
        let b: Box<dyn for<'a> Fn(&'a [u64]) -> BoxFuture<'a, u64>>
            = Box::new(move |n| Box::pin(closure(n)));
        b
    }};
}

async fn example2() {
    let handlers = vec![wrap!(product), wrap!(sum)];
    {
        let numbers = vec![1, 2, 3];
        handlers[0](&numbers).await;
    }
}

This sidesteps the issues of what you can say in a function signature by just not having any function signature.

Thanks, I spent a long time trying to get that lifetime into the right place in the signature and just assumed it must be possible. Using a macro isn't ideal, but the code now compiles at least!

Yeah, as I also said on Discord, the type system just isn't strong enough for this right now.

Perhaps not as a serious suggestion, but I actually managed to define a wrap function, using a lot of nightly unstable features, that does makes this compile:

async fn product(numbers: &[u64]) -> u64 {
    numbers.iter().product()
}
async fn sum(numbers: &[u64]) -> u64 {
    numbers.iter().product()
}

async fn example2() {
    let handlers = vec![wrap(product), wrap(sum)];
    {
        let numbers = vec![1, 2, 3];
        handlers[0](&numbers).await;
    }
}

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.