The self argument is of type &'a mut ReflectionScope<'a> which has the effect that this mutable borrow must exist for the entire time the ReflectionScope<'a> value exists.
This signature is certainly wrong. Why you chose this signature is then the relevant follow-up question. These often come about as a reaction to compiler suggestions to “fix” your code because you did something else wrong… a wait, look here
but it looks a lot like you’re abusing references. Rust references are generally meant to be used for short-term borrows, and rarely come up in data structures in the first place. Furthermore, a reference in a field named “parent” suggests you might be trying to store back-references which is hard to do in Rust, and definitely impossible using ordinary references. (Back references are usually avoided at all cost, and if absolutely necessary, you’d pull out Rc and Weak references possibly need RefCell, too.)
Even if “parent” is no form of “back reference”, it’s still clear that this data structure hasn’t got its ownership story well-defined. Perhaps you could shed some light on how exactly you want to link up these structs and enums (I’ll also try if me re-reading the code once more makes the story become more clear).
On second read, it looks like you might at least not be trying anything cyclic. I mean, if they are and I’m misinterpreting this, then the following thoughts will be irrelevant.
So what I’m thinking is: possibly… if e.g. the code were to work with immutable references &'a ReflectionScope<'a> your problems might go away, since then covariance can help allow such a type to be a shorter borrow, of an original ReflectionScope<'b> with 'b outliving 'a. Perhaps give it a try to see what happens if you wrap the parts that need mutability into Cell or RefCell[1] (it’s unclear from the code snippet alone where the mutable access is needed… perhaps e.g. for the ident_counter? Presumably code, too?) and switch to shared references. No guarantees that this will succeed though, and I don’t know how large your actual code is and how much would need to be refactored ^^
Also feel free to provide or link the complete code in case you want to share the whole context. While I appreciate the summary highlighting the important parts, it can still be faster to answer questions like “what does this part do” or even more importantly “would it compile if I changed such-and-such” with the full code at hand.
Separating the lifetime parameters works for IfBlock, but not for the (mutually) recursive ReflectionScope/ScopeKind (since for distinguishing them all recursively, you’d need an unbounded number of parameters, essentially).
As I understood you I should to wrap some parts of ReflectionScope and ScopeKind with RefCell to fix that, but not understand what concrete parts to wrap. Can you write their definition?
Of course I could only incorporate the code that you showed, so it’s not impossible that these solutions don’t work with some other parts of your code or need to be adjusted. In particular the former approach might need more things mutable, e.g. perhaps the ident_counter wants to be mutated somewhere, too. (For simple types like u64 using Cell can be nicer than RefCell, by the way.) Also in case you use this with multi-threading, RefCell could be limiting.
The reason is that with immutable references, ReflectionScope<'a> becomes covariant in its lifetime argument 'a. This means that the &'a ReflectionScope<'a>-type value passed to child_scope can come from borrowing a ReflectionScope<'b> of a different lifetime 'b that can be larger than 'a. This is because then the ReflectionScope<'b> could be borrowed for a lifetime of 'a, as a &'a ReflectionScope<'b> reference, and due to covariance, this reference can then implicitly be coerced into &'a ReflectionScope<'a>. These kind of coercions can happen in a lot of place in Rust implicitly, including the call to self.reflection_scope.child_scope() in the linked code example.
With the signature changed as indicated above, this kind of coercion would instead happen inside of the body of child_scope. Whereas, with mutable references, doing the same kind of adjustment to the lifetimes in the signature of child_scope makes the compilation error simply move from the body of else_block over to the body of child_scope.