Closure returning LocalBoxFuture, still requires borrowed data to be `'static`

:crab: Classic. Struggle for hours, post something, then solve it soon after.

So the following solves it, using the implicit lifetime technique. The technique:

Where the problem lied (laid?), change:

async fn graph_try_fold<E, Seed, FnFold>(&self, seed: Seed, fn_fold: FnFold)
where
    FnFold: Fn(Seed, &Struct) -> LocalBoxFuture<'_, Result<Seed, E>>,

to:

async fn graph_try_fold<'f, E, Seed, FnFold>(&'f self, seed: Seed, fn_fold: FnFold)
where
    for<'f_fold> FnFold:
        Fn(Seed, &'f_fold Struct, &'f &'f_fold [(); 0]) -> LocalBoxFuture<'f, Result<Seed, E>>,

which means adding an additional argument in the API consumer's closure:

graph.graph_try_fold(1u16, |seed, r#struct| { .. }
graph.graph_try_fold(1u16, |seed, r#struct, _| { .. }

The solved playpen.

Ideally the API consumer doesn't have the additional argument; the "Improving the ergonomics" part of that post is probably reading material.


Another iteration of the solution:

  • no additional param in caller's closure (nicer API)
  • stores limit lifetime in the Graph structure (less nice?)
  • no need to Box the future (yay, nicer API)
code
// futures = "0.3.28"

use std::{future::Future, marker::PhantomData, pin::Pin};

use futures::{stream, FutureExt, StreamExt, TryStreamExt};

pub struct Graph<'env>([Struct<'env>; 1]);
pub struct Struct<'env>(PhantomData<&'env ()>);
pub struct Data(u8);

impl<'env> Graph<'env> {
    async fn graph_try_fold<'f, E, Seed, FnFold, Fut>(&'f self, seed: Seed, fn_fold: FnFold)
    where
        FnFold: Fn(Seed, &'f Struct<'_>) -> Fut,
        Fut: Future<Output = Result<Seed, E>> + 'f,
    {
        let fn_fold = &fn_fold;
        let _ = stream::iter(self.0.iter())
            .map(Result::<_, E>::Ok)
            .try_fold(seed, |seed, r#struct| async move {
                fn_fold(seed, r#struct).await
            })
            .await;
    }
}

pub trait Trait {
    fn trait_fn<'f>(&'f self, data: &'f Data) -> Pin<Box<dyn Future<Output = u16> + 'f>>;
}

impl<'env> Trait for Struct<'env> {
    fn trait_fn<'f>(&'f self, data: &'f Data) -> Pin<Box<dyn Future<Output = u16> + 'f>> {
        async { data.0.into() }.boxed()
    }
}

#[allow(unused_variables)]
pub async fn call_try_fold(graph: &Graph<'_>, data: &Data) {
    graph
        .graph_try_fold(1u16, |seed, r#struct| async move {
            let seed = seed + r#struct.trait_fn(data).await;

            Result::<_, ()>::Ok(seed)
        })
        .await;
}

fn main() {
    let _a = call_try_fold(&Graph([Struct(PhantomData)]), &Data(0));
}
1 Like