2
votes

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
This is the "very tricky" scenario because you'd need to move both node and nodes tied together, with some sort of guarantee that node will never outlive nodes. And of course, the funny thing is that you can't move nodes because it's borrowed by node anyway.Bartek Banachewicz
I should add that really all I care about is the ability to walk through the nodes in the loop. If this were c++, I'd just want a a pointer to the current node. I'm pretty sure I can fix the issue by, for instance, taking all the nodes and wrapping them in Rcs, but I'm hoping there's a somewhat more elegant solution since in principle nodes can live as long as the closure and never needs to move/change.Julian
If there's some clever way to initialize node inside the closure, that would work. I can't see any way to pass state to the closure without initializing it outside, and depending on capture, but if there's a way to do that I'd love to know it.Julian
I'm not sure, but would making node an Option<&Node> and setting it to Some(&nodes[0]) inside the loop work? (possibly with -Z polonius=y?)Solomon Ucko

2 Answers

4
votes

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)

0
votes

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.