Lifetimes and reference chains / scopes

Hi,

I am trying to implement a (special purpose) logging structure where I will sequentially (one thread - no concurrency) add "Values". I want the values tocontain information about the context they where added and have been trying to buils a struct for that.

Please see Rust Playground (rust-lang.org) for a minimal example.

Basically the idea is to have a linked-list of Writers such that whenever a function has a muteable reference to a Writer it can either:

  • Add values to the writer, they will be added with information on the current scope (which the calling function may not know)
  • Create a new "wrapped" Writer - i.e creating a nested scope. Now be able to log to that writer and to pass it to sub-functions.

I am however struggling to extpress this in a way so the borrow checker / lifetimes match.

In the linked playground example for example, i know that on line 78 w3 (from line 67) has been dropped (gone out of scope) and should therefor be able to use w2 again.

I suspect the problem is the implementation of Writer::push where the liftime 'a of self becomes the same as the lifetime of the new scope created, i,e, that when w3 is created at line 67 it will actually have the full lifetime of w2, and thus not be dropped at the cloding brace. Regardless - I have tried various ways to express this pattern such that the lifetime of w3 will only be in the innermost scope and only "borrow" the muteability from w2 but not managed.

Am I barking up the wrong tree here? Is this not the best way to express such a pattern in rust? Or am I simply missing somehting else?

Thank you in advance

This can't be done as you imagined. You can tell this by the anti-pattern &'a mut Type<'a>. A mutable reference to an object with the same lifetime parameter is not usable in the vast majority of cases, because it needs to be mutably (and thus, exclusively) borrowed for its entire lifetime.

In order to fix this, one usually introuces a fresh lifetime parameter, say, 'b: 'a and writes &'a mut Type<'b> instead. This, however, is not possible in the recursive case, because you would need &'a mut Writer<'b, 'c> (in order to distinguish both lifetime parameters from that of the reference), but then 'c would also need to be a lifetime parameter, so now you would have &'a mut Writer<'b, 'c, 'd>, but then 'd would also need to be a new lifetime parameter, etc.

Since immutable borrows don't have the uniqueness requirement, you could "fix" this by switching to dynamic borrow checking using RefCell instead (or Mutex / RwLock if you need thread safety in the future). Then you can usefully materialize a type such as &'a RefCell<Writer<'a>>, like this:

pub struct Writer<'a> {
    parent: Option<&'a RefCell<Writer<'a>>>,
}

impl<'a> Writer<'a> {
    pub fn push(this: &'a RefCell<Self>) -> RefCell<Self> {
        RefCell::new(Writer {
            parent: Some(this),
        })
    }
    
    pub fn add(&mut self) {}
}

Of course, this negates all advantages of having a static borrow checker, because your code will now panic if you accidentally keep a borrow alive longer than you intended to. So perhaps a design around owned values would be more appropriate? I.e. don't store references or RefCells; store Option<Box<Writer>> instead. Or just maintain an explicit stack of writers in a Vec<Writer>, and optionally, store the index of its parent writer in any given child.

2 Likes

Thank you for your long and detailed reply. I think I understand what you are hinting at with using a Option<Box> approach (and agree that a RefCell approach is not ideal) - I will spend the weekend to try out some approaches to make sure I understand what you are actually saying :slight_smile:

Will report back with a solution if anyone looks at this thread in the future.

As a follow-up: I did not quite manageto wrap my head around a version based on owned values, but playgtound implements a version based on Arc<Mutex>

Thanks for the help again

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.