Working around "only a single explicit lifetime bound is permitted"

Hey, hoping someone can help. I'm working on some code where I end up wrapping closures and using the parameters to the outer functions while inside the inner functions. So for a simple example, here is one where we pass a string into an outer function and it returns a closure that will print that string while returning its own input unmodified:

fn simple_wrap<'o>(outer: &'o str) -> impl (for<'i> FnMut(&'i str) -> &'i str) + 'o {
    move |inner: &str| {
        println!("{}", outer);
        inner
    }
}

That works great, but I'm having a problem when I go up to 3 levels. So instead of an outer, and inner we're going to have an outer, middle and inner but do essentially the same thing:

fn wrap_function<'o>(
    outside: &'o str,
) -> impl (for<'m> FnMut(&'m str) -> Box<dyn 'm + for<'i> FnMut(&'i str) -> &'i str>) + 'o {
    move |middle: &str| {
        // Works:
        println!("{:?}", outside);
        Box::new(move |inner: &str| {
            // Breaks if uncommented:
            // println!("{:?}", outside);
            println!("{:?}", middle);
            inner
        })
    }
}

As you can see, I've managed to get the outer lifetime 'o added to the outer closure, but not to the inner closure. I think what I want to do is change:

Box<dyn 'm + ...

to

Box<dyn 'm + 'o + ...

but if I do that then I get:

error[E0226]: only a single explicit lifetime bound is permitted

I also tried adding 'o as a dependency to 'm with

impl (for<'m: 'o> ...

but then you get:

error: lifetime bounds cannot be used in this context

So I'm pretty sure what I need is for that 'o to end up added to the lifetime of the inner closure, but I don't know how to do that. Link to the rust playground.

Then this is a bit of a stretch so it may be more of a distraction from my simplified question above, but the next thing I tried was changing the return type to an impl Trait that I was hoping to use to capture both the middle and inner lifetimes so that I could just add the outer lifetime as the single explicit lifetime bound. So I added in:

Wrapped which is the inner closure:

trait Wrapped<'i> {
    type Output: 'i;
    fn call(&mut self, input: &'i str) -> Self::Output;
}

impl<'i, D: 'i, F> Wrapped<'i> for F
where
    F: FnMut(&'i str) -> D,
{
    type Output = D;
    fn call(&mut self, arg: &'i str) -> D {
        self(arg)
    }
}

and Wrapper which is the outer closure returning an inner closure depending on the outer closure's parameter:

trait Wrapper<'i, 'o, O> {
    type InnerOutput;
    type Inner: 'o + Wrapped<'i, Output = Self::InnerOutput>;
    type Outer: 'o;
    fn call(&mut self, previous_match: Self::Outer) -> Self::Inner;
}

impl<'i, 'o, F, O: 'o, I: 'o + Wrapped<'i>> Wrapper<'i, 'o, O> for F
where
    F: FnMut(O) -> I,
{
    type InnerOutput = I::Output;
    type Inner = I;
    type Outer = O;

    fn call(&mut self, previous_match: Self::Outer) -> Self::Inner {
        self(previous_match)
    }
}

which if we make a function that takes in that type:

fn wrapped_acceptor<AC>(mut accepted: AC)
where
    for<'i, 'o> AC: Wrapper<'i, 'o, &'o str, Outer = &'o str, InnerOutput = &'i str>,
{
    println!("accepted.")
}

then we can call

wrapped_acceptor(wrap_function("foo"));

and everything compiles fine, proving that the return type to wrap_function does successfully map to Wrapper but if I then try to leverage that type by changing the return type of wrap_function to be an impl Wrapper it also does not work.

fn wrap_function_impl_wrapper<'x>(
    outside: &'x str,
) -> impl (for<'i, 'o> Wrapper<'i, 'o, &'o str, Outer = &'o str, InnerOutput = &'i str>) + 'x {
    move |middle: &str| {
        println!("{:?}", outside);
        Box::new(move |inner: &str| {
            // println!("{:?}", outside);
            println!("{:?}", middle);
            inner
        })
    }
}

The context, in case it helps, is I'm working on writing a nom parser that is going to support a look-behind-like functionality where the previous match get passed in and used to decide how the parser functions (so, for example, to do things like match a "*" but only if its preceded by a space). Thats working, but now I'm trying to combine multiple look-behind parsers together to do something like "attempt to match using any look-behind parser in this list" (in nom alt()) and thats why I'm getting into 3-levels deep and hitting this 1 explicit lifetime bound constraint.

One last playground link with that broken code attempting to use impl Wrapper commented out.

For your first attempt, this code appears to work. It just introduces a secondary type to constrain the lifetimes of the HRTB. But I'm worried it's more restrictive than necessary - there's not reason 'm can't live longer than 'o, but my code requires 'o: 'm to compile. Still, you can try it.

1 Like

Yeah, luckily I don't think that extra restriction is going to cause a problem in my code base so this works perfectly. I can just add another impl Wrapper for this new Inner type and then it should plug into the rest of my code nicely. Thank you!

Here's a version that removes the lifetime restriction I believe

2 Likes

Oh nice, even better! Thank you!