Make child lifetimes separate from each other but still bounded by parent lifetime

I've got a "parent" and a "child". All child instances hold a shared reference to the parent and they can produce siblings:

I'll use a simplification of the code rather than post it all here. These are all the basic definitions:

#[derive(Debug)]
struct Parent {
    val: i32,
}

impl Parent {
    fn child(&self) -> Option<Child> {
        if self.val > 0 {
            Some(Child::new(self.val - 1, self))
        } else {
            None
        }
    }
}

#[derive(Debug)]
struct Child<'p> {
    val: i32,
    parent: &'p Parent,
}

impl<'p> Child<'p> {
    fn new(val: i32, parent: &'p Parent) -> Child<'p> {
        Self { val, parent }
    }

    fn sibling(&self) -> Option<Child> {
        if self.val > 0 {
            Some(Child {
                val: self.val - 1,
                parent: self.parent,
            })
        } else {
            None
        }
    }
}

Now, I'd love to chain up calls for getting siblings as follows:

fn usage<'p>(p1: &'p Parent) -> Option<Child<'p>> {
    let c = p1.child()?.sibling()?.sibling()?;
    // do stuff with c before returning it
    Some(c)
}

fn usage_with_iter<'p>(p1: &'p Parent) -> Option<Child<'p>> {
    let mut c = p1.child()?;
    for _ in 0..4 {
        c = c.sibling()?;
    }
    Some(c)
}

However, both fail to compile:

error[E0515]: cannot return value referencing temporary value
  --> src/bin/main.rs:53:5
   |
52 |     let c = p1.child()?.sibling()?.sibling()?;
   |             ---------------------- temporary value created here
53 |     Some(c)
   |     ^^^^^^^ returns a value referencing data owned by the current function

error[E0506]: cannot assign to `c` because it is borrowed
  --> src/bin/main.rs:59:9
   |
56 | fn usage_with_iter(p1: &Parent) -> Option<Child> {
   |                        - let's call the lifetime of this reference `'1`
...
59 |         c = c.sibling()?;
   |         ^^^^-^^^^^^^^^^^
   |         |   |
   |         |   `c` is borrowed here
   |         `c` is assigned to here but it was already borrowed
60 |     }
61 |     Some(c)
   |     ------- returning this value requires that `c` is borrowed for `'1`

error[E0515]: cannot return value referencing local variable `c`
  --> src/bin/main.rs:61:5
   |
59 |         c = c.sibling()?;
   |             - `c` is borrowed here
60 |     }
61 |     Some(c)
   |     ^^^^^^^ returns a value referencing data owned by the current function

I could fix the first usage by separating out the calls and adding an explicit lifetime at the sibling method

#[derive(Debug)]
struct Child<'p> {
    val: i32,
    parent: &'p Parent,
}

impl<'p> Child<'p> {
    // ... 
    fn sibling(&self) -> Option<Child<'p>> {
        if self.val > 0 {
            Some(Child {
                val: self.val - 1,
                parent: self.parent,
            })
        } else {
            None
        }
    }
}

fn usage(p1: &Parent) -> Option<Child> {
    let c = p1.child()?;
    let c2 = c.sibling()?;
    let c3 = c2.sibling()?;
    Some(c3)
}

However, I can't separate out the calls for usage_with_iter since I can't tell ahead of time how many I'll have to make.

Either way, the fix seemed iffy to me since each child's lifetime should be entirely independent of the other (once a child produces a sibling the only field/resource they share is the reference to the parent).

I knew I needed a way to de-link all the childs' lifetimes from each other so after a bunch of trial and error I arrived at the following solution - using phantom lifetimes:

#[derive(Debug)]
struct Child<'p, 'c> {
      val: i32,
      parent: &'p Parent,
      _child_lifetime: PhantomData<&'c ()>,
}

impl<'p, 'c> Child<'p, 'c> {
      fn new(val: i32, parent: &'p Parent) -> Child<'p, 'c> {
        Self {
            val,
            parent,
            _child_lifetime: PhantomData,
        }
    }

    fn sibling<'x>(&self) -> Option<Child<'x, 'p>>
    where
        'p: 'x,
      {
        if self.val > 0 {
            Some(Child {
                val: self.val - 1,
                parent: self.parent,
                _child_lifetime: PhantomData,
            })
          } else {
            None
        }
    }
}

My questions are as follows:

  • Is it actually correct?
  • Is there a simpler solution?
  • I arrived at this solution in an ad-hoc manner, how might one approach it from first principles - is there some detail I missed out in the rust book that would help in understanding how to use lifetimes in this manner when I face a similar problem in future

IMO the fix is correct precisely for this reason, as it is telling the compiler that the new Child is borrowing the parent (with 'p) rather than the other Child (when you don't specify the lifetime it will default to the one of the &self, which is the lifetime of the Child you're creating the sibling from).

I'm not sure what you're trying to get at with the phantom lifetime example, as it seems the 'c lifetime is never used. Moreover here:

There's no reason for 'x to be anything but 'p, making it useless.

3 Likes

You're right, I made some other mistake elsewhere and the phantom lifetime is not needed for both kinds of usages.

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.