How can I express this closure lifetime for static dispatch?

I'm attempting to have a function that takes in a borrowed parameter under one lifetime and return a closure that uses that value while receiving a parameter of a different lifetime. I've managed to get it to work with:

fn wrap_function<'i, 'o>(outer: &'o str) -> Box<dyn 'o + FnMut(&'i str) -> &'i str> {
    Box::new(move |i: &'i str| {
        println!("outer: {}", outer);
        i
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn test_it() {
        let x = "inner_val";
        let y = wrap_function("outer_value")(x);
        assert_eq!(x, y);
    }
}

So you can see that I have an outer lifetime for the outer borrow, and an inner lifetime for the parameter and return type of the closure, but the closure still depends on that outer reference so I added the 'o lifetime inside the box in the return type.

My problem is: how do I express that for static dispatch? If I convert it to impl function and comment out the println then it works:

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

but if I add 'o to the return type lifetime and uncomment the println then it fails to build:

fn wrap_function<'i, 'o>(outer: &'o str) -> impl FnMut(&'i str) -> &'i str + 'o {
    move |i: &'i str| {
        println!("outer: {}", outer);
        i
    }
}
error[E0482]: lifetime of return value does not outlive the function call
 --> src/parser/testground.rs:1:45
  |
1 | fn wrap_function<'i, 'o>(outer: &'o str) -> impl FnMut(&'i str) -> &'i str + 'o {
  |                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
note: the return value is only valid for the lifetime `'i` as defined on the function body at 1:18
 --> src/parser/testground.rs:1:18
  |
1 | fn wrap_function<'i, 'o>(outer: &'o str) -> impl FnMut(&'i str) -> &'i str + 'o {

and attempting to do it with generics also errored out:

fn wrap_function<'i, 'o, F>(outer: &'o str) -> F
where
    F: 'o + FnMut(&'i str) -> &'i str,
{
    move |i: &'i str| {
        println!("outer: {}", outer);
        i
    }
}
error[E0308]: mismatched types
 --> src/parser/testground.rs:5:5
  |
1 |   fn wrap_function<'i, 'o, F>(outer: &'o str) -> F
  |                            - this type parameter - expected `F` because of return type
...
5 | /     move |i: &'i str| {
6 | |         println!("outer: {}", outer);
7 | |         i
8 | |     }
  | |_____^ expected type parameter `F`, found closure
  |
  = note: expected type parameter `F`
                    found closure `[closure@src/parser/testground.rs:5:5: 8:6]`
  = help: every closure has a distinct type and so could not always match the caller-chosen type of parameter `F`

The context is I'm trying to implement a look-behind kind of parser in nom where the previously parsed value is passed into the outer generator returning a nom parser that could use that value to influence its parsing. But I don't think that matters too much since I've managed to whittle the issue down to such a small example.

1 Like

The compiler accepts the original version (without the F type parameter) if you add the bound 'i: 'o, but that might be more restrictive than what you need here, I'm not sure. I think the issue is that the returned FnMut could be called at any point governed by 'o, so the compiler tries to ensure that its return value will be valid anywhere in 'o, which requires that 'i outlive 'o… but I'm not sure why that validity isn't already guaranteed by the fact that the closure takes a &'i str as input. [Edit: I suppose if 'i were shorter than 'o then the closure could still exist for all of 'o, but you could only call it within 'i. And I'm guessing that returning impl FnMut(...) -> ... + 'o somehow requires that you can call it anywhere in 'o, not just that you can keep it around for 'o. But it's only a guess.]

Here's another version using a HRTB that's probably closer to what you want, and that compiles:

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

You can even elide the lifetime 'o by using + '_ on the return type.

2 Likes

Thank you! This is perfect! The two bits I was missing were:

  1. I didn't know you could put a for<> between impl and FnMut. I had attempted it with for<> before impl but that was a syntax error so I assumed for<> wasn't possible with impl Trait.
  2. I didn't know I could use parenthesis in the return type without it turning into a tuple.
1 Like

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.