Beginner Lifetime Problem

Hi, I'm trying to work through the Crafting Interpreters tutorial in Rust to help me learn the language and I'm getting stuck when it comes to nesting 'environments'. My code is here:


https://github.com/michaeljones/rox/blob/1dbb2e7e96e37fa696b849e5d9118379e8e1bf38/src/environment.rs

When I compile I get the following errors:

error[E0499]: cannot borrow `*environment` as mutable more than once at a time
  --> src/interpreter.rs:25:47
   |
23 |     pub fn interpret<'a>(&mut self, statements: &Vec<Stmt>, environment: &'a mut Environment<'a>) {
   |                      -- lifetime `'a` defined here
24 |         for statement in statements {
25 |             self.execute_statement(statement, environment);
   |             ----------------------------------^^^^^^^^^^^-
   |             |                                 |
   |             |                                 mutable borrow starts here in previous iteration of loop
   |             argument requires that `*environment` is borrowed for `'a`

error[E0597]: `block_environment` does not live long enough
  --> src/interpreter.rs:33:48
   |
29 |     fn execute_statement<'a>(&mut self, statement: &Stmt, environment: &'a mut Environment<'a>) {
   |                          -- lifetime `'a` defined here
...
33 |                 self.execute_block(statements, &mut block_environment);
   |                 -------------------------------^^^^^^^^^^^^^^^^^^^^^^-
   |                 |                              |
   |                 |                              borrowed value does not live long enough
   |                 argument requires that `block_environment` is borrowed for `'a`
34 |             }
   |             - `block_environment` dropped here while still borrowed

error[E0499]: cannot borrow `*environment` as mutable more than once at a time
  --> src/interpreter.rs:64:47
   |
62 |     fn execute_block<'a>(&mut self, statements: &Vec<Stmt>, environment: &'a mut Environment<'a>) {
   |                      -- lifetime `'a` defined here
63 |         for statement in statements {
64 |             self.execute_statement(statement, environment);
   |             ----------------------------------^^^^^^^^^^^-
   |             |                                 |
   |             |                                 mutable borrow starts here in previous iteration of loop
   |             argument requires that `*environment` is borrowed for `'a`

error: aborting due to 3 previous errors

I've tried googling around but I'm stuck. It feels like the compiler is wanting to borrow enviroment for longer than I want it to when passing it into execute_statement but I can sort of see from the lifetime annotations why that might happen but I don't know how to make it borrow it for less time (just one iteration of the loop.)

I thought maybe trying to create a new reference? Or googling how to make a new lifetime that is shorter or something?

I imagine I might have to restructure things but I've no idea how. I'm already straying from the path the author takes in the book as he is using Java and just juggles some references around.

Clearly I'm new to this and flailing around. I'm sorry that I've just linked to a whole repository but I think the issues are isolated to the two files and often it is best to see the actual code that is wrong rather than my attempt to simplify it.

Any help would be much appreciated.

The part of the tutorial that I'm following along with at the moment is here: http://craftinginterpreters.com/statements-and-state.html#nesting-and-shadowing

This is generally a bad idea:

&'a mut Environment<'a>

When reusing the same lifetime parameter, you are telling Rust that these must be the same lifetimes.

pub fn interpret<'a>(&mut self, statements: &Vec<Stmt>, environment: &'a mut Environment<'a>) {
    for statement in statements {
        // What is the lifetime on the `environment` that `execute_statement` receives?
        self.execute_statement(statement, environment);
    }
}
fn execute_statement<'b>(&mut self, statement: &Stmt, environment: &'b mut Environment<'b>) { ...

In this case you are passing an &'b mut Environment<'b> to execute_statement. I am using 'b to display that it is allowed to be distinct from the 'a in interpret. And normally this would be fine, because we could choose 'b as the region of a single iteration and all would be good. This is because you can reborrow an &'a mut T to an &'b mut T when 'b is smaller than 'a.

The issue is that this reborrowing produces an &'b mut Environment<'a>. It doesn't change the inner lifetime on Environment. And because mutable references are something called invariant, you can't replace that 'a with a smaller lifetime. (it would work with immutable references)

The problem is now that execute_statement reuses the lifetime in both positions. This forces the two lifetimes to be equal, and since it can't change the inner lifetime, it will change the outer one. But this means that your reborrow is borrowed for the lifetime 'a, and this lifetime contains the entire loop, hence your error.

The best solution is probably to just not use the same lifetime parameter in both positions to allow them to be different. I have not tested if it compiles or reveals additional problems.

Thank you for the quick response! I have tried to play around with separating those (though I'm just stabbing in the dark) but I think an issue is that I've got Environment declared as:

pub struct Environment<'a> {
    enclosing: Option<&'a mut Environment<'a>>,
    values: HashMap<String, Option<Value>>,
}

Should I try to somehow change this declaration to use two different lifetimes too?

It may not be necessary, but it doesn't hurt.

Sorry, I've just realised I didn't push the environment.rs file in the end. I've just done so now with the changes.

I've added an extra lifetime parameter but due the nested definition of Environment it seems really hard to have independent lifetimes for the reference and Environment. I've now got:

pub struct Environment<'a, 'b> {
    enclosing: Option<&'a mut Environment<'a, 'b>>,
    values: HashMap<String, Option<Value>>,
}

Instead of but that still has the lifetime repeated between the & and the <> and then I ultimately get the same error as before. I think I understand your explanation so thank you for that.

Ah yeah. You might not be able to do it in the definition. You can try without.

Scanning through a bunch of other people's efforts at following this tutorial in Rust it seems like quite a few use RefCell's and at least one stores environments on a stack instead of nesting them. Perhaps either approach is preferable or necessary over what I'm trying to do given Rust's ownership model.

Thanks again for the help!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.