Can't borrow reference to structure in captured tree because it doesn't live long enough

Viewed 147

I have a tree structure with a node and children, and a loop from a GUI library which expects a function to run on each iteration. I'm struggling to get the borrow checker to let me keep a reference to the node I'm processing - it complains that nodes doesn't live long enough.

Here's a minimal reproduction:

#[derive(Debug)]
struct Node {
    value: u64,
    children: Vec<Node>,
}

fn run_loop<F>(mut handler: F)
where
    F: 'static + FnMut(),
{
    for _ in 0..500 {
        handler();
    }
}

fn main() {
    let nodes = vec![
        Node {
            value: 1,
            children: vec![Node {
                value: 3,
                children: vec![],
            }],
        },
        Node {
            value: 2,
            children: vec![],
        },
    ];
    let mut node = &nodes[0];

    run_loop(move || {
        println!("Node: {:?}", node);
        node = &node.children[0];
    });
}
error[E0597]: `nodes` does not live long enough
  --> src/main.rs:30:21
   |
30 |       let mut node = &nodes[0];
   |                       ^^^^^ borrowed value does not live long enough
31 | 
32 | /     run_loop(move || {
33 | |         println!("Node: {:?}", node);
34 | |         node = &node.children[0];
35 | |     });
   | |______- argument requires that `nodes` is borrowed for `'static`
36 |   }
   |   - `nodes` dropped here while still borrowed

Rust Playground

What's the best way to make this work? I can't change the structure of run_loop. Ideally I wouldn't change the structure of Node (it's an object returned from a third-party library so while I could parse the object out into a new data structure, that wouldn't be elegant). Can I make the borrow checker happy with this just making changes in main?

2 Answers

it complains that nodes doesn't live long enough.

That's because it doesn't. The run_loop function requires its argument to live forever ('static). The nodes variable does not live forever, and consequently the closure that captures it does not live forever.

The easy fix would be to change run_loop to not require an argument that lives forever (by removing the 'static constraint), but if you cannot do that then you can just make nodes live forever instead. You can do this by "leaking" it.

let nodes = vec![ /*...*/ ];
let nodes = Vec::leak(nodes);
let mut node = &nodes[0];

(Playground link)

At the moment, this requires nightly, but there is a similar leak function in Box in stable.

let nodes = vec![ /*...*/ ];
let nodes = Box::leak(nodes.into_boxed_slice());
let mut node = &nodes[0];

(Playground link)

The leak solution didn't work for my actual use case, and in any case doesn't really represent the semantics of the situation or generalize very well (what if you don't want to leak the contents forever? Or what if it's not a vector you're working with?).

I ended up deciding that the best solution was just to do unsafe pointer manipulation:

let nodes = Box::pin(nodes);
let mut node_ptr = std::ptr::NonNull::from(&nodes[0]);

run_loop(move || {
    let node = unsafe { node_ptr.as_ref() };
    println!("Node: {:?}", node);
    node_ptr = std::ptr::NonNull::from(&(node.children[0]));
});

In my actual implementation I put both nodes and node_ptr in a single struct so that provides some guarantee that the nodes won't be dropped before node_ptr.

I'm going to leave this open since I'd love to see a solution that doesn't require unsafe, but am posting this here since for now at least it's the best I have.

Related