Fixing borrowed data escapes outside of closure

With inference, closure1 works fine, at the cost of not knowing the type.
However, if the type is required, e.g. use_node(&node.line) in closure2 then I have no idea how to make it work.

struct Line;

struct Node {
    line: Line,
}

fn main() {
    let node = Node { line: Line };
    let mut v: Vec<&Node> = Vec::new();

    let mut closure1 = |node| {
        v.push(node);
    };
    closure1(&node);
    
    let mut closure2 = |node: &Node| {
        use_node(&node.line);

        v.push(node);
    };
    closure2(&node);
}

fn use_node(_: &Line) {
    todo!()
}

error[E0521]: borrowed data escapes outside of closure
  --> src/main.rs:19:9
   |
9  |     let mut v: Vec<&Node> = Vec::new();
   |         ----- `v` declared here, outside of the closure body
...
16 |     let mut closure2 = |node: &Node| {
   |                         ---- `node` is a reference that is only valid in the closure body
...
19 |         v.push(node);
   |         ^^^^^^^^^^^^ `node` escapes the closure body here

Your closure needs to take a &Node with one particular lifetime (matching that in the type of v), but when the compiler sees the &Node annotation, it thinks you want it to make a closure that takes any lifetime.

Workaround one: put the annotation in the closure body instead.

    let mut closure2 = |node| {
        let node: &Node = node;
        use_node(&node.line);

        v.push(node);
    };

Workaround two: funnel the closure through an identity function with the bounds you want.

    // N.b. a closure that takes a `&Node` with one specific lifetime,
    // not a closure that takes a `&Node` with any lifetime
    fn funnel<'a, F: FnMut(&'a Node)>(f: F) -> F { f } 

    let mut closure2 = funnel(|node| {
        use_node(&node.line);
        v.push(node);
    });
2 Likes

Your solutions make sense, knowing that lifetimes for closure parameters are higher order ranked (which I just learned recently). But for most people this would be very unexpected and the error message was not helpful. Do you (or does anyone) happen to know whether any improvements to closures are planned in this area? I don't see an RFC, doing a quick scan on the titles.

What does higher order mean? How/Where did you learn it?

I think I've heard it in discussions a few times, but it finally sunk in recently here:

I found the following pre-RFC discussion where these issues are mentioned, but I'm not sure they will be addressed.

Oh, it is an RFC, somehow I missed it earlier:

You found that one RFC, but it doesn't help with this problem ("compiler thought higher-ranked, I need single lifetime"), just the opposite problem ("compiler thought single lifetime, I need higher-ranked"). Local impl Trait annotations (see the code block below) could conceivably be a step forward. I don't know off the top of my head if there's been any more recent efforts.

The accepted RFC also doesn't help with unnameable types, e.g. if your return type is unnameable but captures input lifetimes. As we keep getting more async fn, return type impl Traits, TAITs, APITs, gen fn... which all have unnameable return types... Rust's shortcomings in this area we continue to become more burdensome.

So there will probably be some more thorough way of annotating closures eventually, be it impl Trait annotations or something else.[1] The need for something more thorough might be a barrier to having the more limited RFC stabilize.

Not-higher-ranked-when-you-wanted-to-be is the more common problem, so it's also possible that whatever the future solution is also has this shortcoming with regards to input lifetimes! For example, the sugar for fn(..) and dyn Fn(..) turns elided input lifetimes into higher-ranked lifetimes (ala fn declarations). If you need one that takes a non-higher ranked lifetime, and that lifetime doesn't have a name, it can be tricky or impossible to name the name.[2]

So if what we get is limited to

let lambda: impl Fn(&Node) = |node| ...;
// aka
// let lambda: impl for<'a> Fn(&'a Node) = |node| ...;

the OP may still have been out of luck, say.


  1. At least I hope so, as the alternative is another "we'll try to infer everything for you without giving you a way to override it, and surely it'll be good enough this time". ↩︎

  2. This came up on the forum once, and IIRC I wasn't able to solve it at that time. ↩︎

1 Like

Thanks for the explanation!

I have a variant of the problem and I don't understand why workaround 2 didn't work

struct Line<'a> {
    // dummy field to illustrate a refernce
    s: &'a str,
}

struct Node<'a> {
    line: Line<'a>,
}

fn main() {
    let mut v: Vec<&Node> = Vec::new();

    for _ in 0..10 {
        // N.b. a closure that takes a `&Node` with one specific lifetime, not a
        // closure that takes a `&Node` with any lifetime
        fn funnel<'a, F: FnMut(&'a Node)>(f: F) -> F {
            f
        }

        let mut closure2 = funnel(|node| {
            v.push(node);
        });

        some_work(&mut closure2)
    }
}

fn some_work<F>(handle_node: &mut F)
where
    F: FnMut(&Node),
{
}

error: implementation of `FnMut` is not general enough
  --> src/main.rs:24:9
   |
24 |         some_work(&mut closure2)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnMut` is not general enough
   |
   = note: closure with signature `for<'a> fn(&'2 Node<'a>)` must implement `FnMut<(&'1 Node<'_>,)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnMut<(&'2 Node<'_>,)>`, for some specific lifetime `'2`

I am not sure if it is because the funneled closure only works for a specific lifetime but the F in some_work expecting a closure that works for all lifetime.

That is exactly why. Change the bound on some_work to look like the one on funnel. (Though it may not be of much use if the &Nodes you're going to pass to F aren't also supplied.)

1 Like