Producing objects that outlive the parent struct without borrowing

I'm trying to model a behavior where a Parent produces Intermediate objects that, when finalized, will become Leaf. I would like to make it so that Parent is mutably borrowed while working with Intermediate, thus ensuring there's at most one such objects for each Parent that I have.

I've been able to do that without problems so far, but it's my last requirement that's making this difficult. I would also like for no Leaf object to outlive the Parent it came from. But this doesn't seem possible without immutably borrowing Parent and preventing any other Intermediate objects from being created while a Leaf exists.

I'm posting simplified code below. I tried a more involved solution with another object to place the borrows on (allowing &mut Parent to exist independently) and then unifying the lifetime of it with Parent, but that has its own problems.

use std::marker::PhantomData;

struct Parent;

struct Intermediate<'p>(&'p mut Parent);

struct Leaf<'p>(PhantomData<&'p Parent>);

impl Parent {
    fn produce<'p>(&'p mut self) -> Intermediate<'p> {
        Intermediate(self)
    }
}

impl<'p> Intermediate<'p> {
    fn finish(self) -> Leaf<'p> {
        Leaf(PhantomData)
    }
}

fn main() {
    let mut parent = Parent;
    
    let intermediate = parent.produce();
    let leaf1 = intermediate.finish();
    
    let intermediate = parent.produce();
    let leaf2 = intermediate.finish();
    
    // I want this order of destructors
    drop(leaf2);
    drop(leaf1);
    drop(parent);
}

Is there a way to do it without runtime checking & smart pointers?

You need an extra layer.

struct Parent;

struct IntermediateProducer<'p>(&'p mut Parent);

struct Intermediate<'c, 'p>(&'c mut Parent, PhantomData<&'p Parent>);

struct Leaf<'p>(PhantomData<&'p Parent>);

impl Parent {
    fn producer<'p>(&'p mut self) -> IntermediateProducer<'p> {
        IntermediateProducer(self)
    }
}

impl<'p> IntermediateProducer<'p> {
    fn produce<'c>(&'c mut self) -> Intermediate<'c, 'p> {
        Intermediate(self.0, PhantomData)
    }
}

impl<'c, 'p> Intermediate<'c, 'p> {
    fn finish(self) -> Leaf<'p> {
        Leaf(self.1)
    }
}

playground

I don't think it is possible if the leaf actually needs to hold a reference to the parent, but it works with PhantomData.

1 Like

Thanks a lot, this is perfect because my Leaf does not need to hold a reference.

I've not worked with PhantomData before. Does this also work without introducing a new layer?:

use std::marker::PhantomData;

struct Parent;

struct Intermediate<'i, 'p>(&'i mut Parent, PhantomData<&'p Parent>);

struct Leaf<'p>(PhantomData<&'p Parent>);

impl Parent {
    fn produce<'i, 'p>(&'i mut self) -> Intermediate<'i, 'p> {
        Intermediate(self, PhantomData)
    }
}

impl<'i, 'p> Intermediate<'i, 'p> {
    fn finish(self) -> Leaf<'p> {
        Leaf(self.1)
    }
}

fn main() {
    let mut parent = Parent;
    
    let intermediate = parent.produce();
    let leaf1 = intermediate.finish();
    
    let intermediate = parent.produce();
    let leaf2 = intermediate.finish();
    
    // I want this order of destructors
    drop(leaf2);
    drop(leaf1);
    drop(parent);
}

No, because 'p is unconstrained, so the Leaf may outlive the Parent.

1 Like

Ah , I see that now.