Help with lifetimes in loops

I've been making a quick an terrible code interpreter. However, when i was adding scopes to the execution all hell broke lose in this function. I've tried everything and in all other parts of the codebase it works. Now I can obviously refactor the code, but it feels like this should work. Can anyone explain this behaviour or what I'm obviously missing.

pub fn execute_body<'a,'b, T:Iterator<Item = &'a Statement>>(body:T,context:&'b mut ExecutionContext,scope:&'b mut Scope<'b>)->Rc<RefCell<Variable>>{
    let mut last_rtn = None;
    let mut skip_next = false;
    let mut last_if_check = false;
    for stm in body {
        if !skip_next {
            let a = execute_statement(stm, context,scope,Meta::default());
            last_rtn = Some(a.clone());
        }
        skip_next = false;
        if let Some(ref a) = last_rtn {
            let r = a.borrow();
            match *r {
                Variable::DisallowExecuteNextStatement=>{skip_next = true;last_if_check=false},
                Variable::AllowExecuteNextStatement=>{skip_next = false;last_if_check=true},
                _=>{}
            }
        }
        match stm{
            Statement::ElseDirective=>{skip_next = last_if_check}
            _=>{}
        }
    }
    last_rtn.unwrap_or(context.undefined.clone())
}

I've toyed around with the life time parameters but it always results in roughly the same error. And again oddly enough every worked without the scope parameter

cannot borrow `*context` as mutable more than once at a time
`*context` was mutably borrowed here in the previous iteration of the loop

Can you post the signature of execute_statement as well?

surething:

pub fn execute_statement<'a>(stm:&Statement,context:&'a mut ExecutionContext,scope:&'a mut Scope<'a>,meta:Meta)->Rc<RefCell<Variable>>

&'a mut Type<'a> is almost always wrong. Please search this forum for similar issues for an explanation. You'll likely need another lifetime parameter (if you want to be explicit; or just leaving off one of them should work, depending on which one you actually need).

If this function is putting context into scope, then it should probably be

pub fn execute_statement<'a>(
    stm: &Statement,
    context: &'a mut ExecutionContext,
    scope: &mut Scope<'a>,
    meta: Meta,
) -> Rc<RefCell<Variable>>

Otherwise, get rid of the lifetime (they will all be inferred as different lifetimes).

pub fn execute_statement(
    stm: &Statement,
    context: &mut ExecutionContext,
    scope: &mut Scope,
    meta: Meta,
)

It's not good practice to completely leave off the lifetime parameter of a generic type, though (because it's then syntactically unclear whether it has a lifetime). Specifically, the second variation would be better written as

pub fn execute_statement(
    stm: &Statement,
    context: &mut ExecutionContext,
    scope: &mut Scope<'_>,
    meta: Meta,
)

Also, use the elided_lifetimes_in_paths lint.

4 Likes

From execute_statement's signature:

  • Scope<'a>: 'a must live at least as long as Scope.
  • &'a mut Scope: Scope must live at least as long as 'a, which is the lifetime of the mutable borrow.
  • Combined: the lifetime of the mutable borrow matches the lifetime of Scope; it can never be borrowed from again.
  • context:&'a mut ExecutionContext, scope:&'a mut Scope<'a>: ExecutionContext's mutable borrow lasts just as long.

First time through the loop:

  • execute_statement(..., context, scope, ...)
  • This mutably borrows scope for the remainder of its lifetime and borrows context for just as long.
2 Likes

Thanks for the reply. Seems logical but the lifetime bounds on execute_statement are required due to this piece of code:

let mut s = Scope { parent_scope: Some(scope), ..Default::default() };
execute_body(b.iter(), context,&mut s)

scope here wraps around another scope. Maybe there some magic way to rewrite this

Nevertheless, they do render your scope object useless, so you won't be able to get far with that signature. You will need to change it and likely refactor its callers.

I suspect you overconstrained a lifetime somewhere then that cascaded on you, propagating across types and functions. We may need to see the whole thing to detangle the lifetimes.

1 Like

Other possibilities. All these questions are about & and &mut. Rc and Arc are more permissive.

  • Do you have any reference loops? x references y which references x?
  • Do you have any internal references? x.y references x.z?
  • Do you cause older objects to reference newer objects? e.g. create x, then create y, then cause x.y to reference y.
1 Like

I've refactored now so that scopes now longer mut ref other scopes and that all scopes are owned by one object which also keeps track of the scope hierarchy

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.