Can't solve this "does not live long enough" error


#1

After 4 months of pausing Rust development I tried to solve my port of the super tiny compiler again. But it looks like I can’t solve it, even after a pause…

The original super tiny compiler used a hack. Now I created a JavaScript version without a hack. I tried to port this “hack-free” version to Rust, but I’m stuck here.

I have a struct Context which holds a Vec. I push to the Vec sometimes, but then I want to replace it with a different Vec. All of this happens in closure.

            enter: Some(Box::new(|
                node: &Node,
                parent: Option<&Node>,
                context: &mut Context,
            | {
                if let Node::CallExpression { ref name, .. } = *node {
                    let arguments: Vec<TransformedNode> = vec![];

                    let parent_nodes = &mut context.nodes;
                    context.nodes = &mut arguments;

                    let call_expression = TransformedNode::CallExpression {
                        callee: Box::new(TransformedNode::Identifier(name.to_string())),
                        arguments: arguments,
                    };

                    let expression = match parent {
                        Some(&Node::CallExpression { .. }) => call_expression,
                        _ => {
                            TransformedNode::ExpressionStatement {
                                expression: Box::new(call_expression),
                            }
                        }
                    };
                    parent_nodes.push(expression);
                }
            })),

But I get this error:

error[E0597]: `arguments` does not live long enough
   --> src/lib.rs:261:42
    |
261 |                     context.nodes = &mut arguments;
    |                                          ^^^^^^^^^ does not live long enough
...
277 |                 }
    |                 - borrowed value only lives until here
    |
note: borrowed value must be valid for the anonymous lifetime #5 defined on the body at 252:34...
   --> src/lib.rs:252:34
    |
252 |               enter: Some(Box::new(|
    |  __________________________________^
253 | |                 node: &Node,
254 | |                 parent: Option<&Node>,
255 | |                 context: &mut Context,
...   |
277 | |                 }
278 | |             })),
    | |_____________^

error[E0506]: cannot assign to `context.nodes` because it is borrowed
   --> src/lib.rs:261:21
    |
260 |                     let parent_nodes = &mut context.nodes;
    |                                             ------------- borrow of `context.nodes` occurs here
261 |                     context.nodes = &mut arguments;
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `context.nodes` occurs here

error: aborting due to 2 previous errors

What is the best way to solve this?


#2

It looks like you’re trying to accumulate arguments (TransformedNode) in the context until you encounter a CallExpression, at which point you want to transform it and use the arguments collected in the context thus far as the arguments for the transformed node. The transformed node is added to the “old context vec” (this is what you’re trying to do). After that, you want to start fresh accumulation of arguments for the next node. Etc. Is that right?

It seems like you’d have an easier time if you “popped off” the nodes in the context and pushed them to a new vec that is the set of arguments to a transformed node (owned by that node). In other words, instead of trying to maintain references to the nodes that serve as arguments, make them owned by the transformed node. So context would just be a transient vec (i.e. evaluation stack of in-progress nodes); once a transform is found, the nodes in the context are taken out of that vec and added to the transformed node - the context vec is cleared in the process.


#3

Sounds good. Thank your for taking the time to reflect this.

I’m not sure, if I understand this. I transform one AST (my Node enum) into a different AST (my TransformedNode enum). The Node and TransformedNode don’t have references to each other, so I don’t know if a Node can own a TransformedNode or the other way around?

EDIT:

Do you think of changing the closure signature to something like this and push directly to the transformed_node.body/transformed_node.expression (if possible)?

            enter: Some(Box::new(|
                node: &Node,
                parent: Option<&Node>,
                transformed_node: &mut TransformedNode,
            | {

#4

I think the gist of what I’m trying to say (but probably failing in making it clear) is that when you create a new CallExpression it needs to own the “argument” nodes vec (so not a reference to the Context’s vec but a vec that the CallExpression owns). So essentially the CallExpression “consumes” the nodes you’ve collected thus far. That consumption should wipe the Context vec and push your transformed node back onto the context vec (so multiple input nodes are replaced with this CallExpression node). To do that, the CallExpression should have its own vec (not a reference to the context vec) - this vec is populated by removing the nodes from the context vec (and thus emptying it in the process) - the CallExpression’s vec is now the owner of those nodes, not the context’s vec.

I think that’s the idea that most closely fits your intentions, but maybe I’m missing something.


#5

Thank you. I’ll try to read this carefully and try to apply it to my code :+1: