'How to make a lifetime constraint "pass through" from object to its grandchild?

I'm doing some experiments with lifetimes and the borrow checker. Imagine this first struct:

struct First {}

impl First {
    fn new() -> Self {
        Self {}
    }

    fn second(&self) -> Second {
        Second::new(self)
    }

    fn hello(&self) {
        println!("Hello");
    }
}

And the second, which has a lifetime constraint that depends on First:

struct Second<'a> {
    owner: &'a First,
}

impl<'a> Second<'a> {
    fn new(owner: &'a First) -> Self {
        Self { owner }
    }

    fn hello(&self) {
        self.owner.hello();
    }
}

The code above works perfectly fine: Second is created by First, and it cannot outlive First.

The problem

Now let's modify Second so it can create a third struct, Third:

struct Second<'a> {
    owner: &'a First,
}

impl<'a> Second<'a> {
    fn new(owner: &'a First) -> Self {
        Self { owner }
    }

    fn third(&self) -> Third {
        Third::new(self.owner)
    }

    fn hello(&self) {
        self.owner.hello();
    }
}

And Third itself, which also depends on First:

struct Third<'a> {
    owner: &'a First,
}

impl<'a> Third<'a> {
    fn new(owner: &'a First) -> Self {
        Self { owner }
    }

    fn hello(&self) {
        self.owner.hello();
    }
}

I would imagine that, when creating an instance of Third, it would depend on First, but that's not the case. Actually Third depends on Second:

fn main() {
    let f = First::new();
    let t = {
        let sss = f.second();
        sss.third() // error: sss does not live long enough
    };
}

So, how can I make the lifetime constraint "pass through" from First to Third?

Full playground.



Solution 1:[1]

Rust has rules how it infers lifetimes for functions: the lifetime elision rules.

And those rules state that:

  • Each elided lifetime (i.e. a type that should had have a lifetime, but doesn't, like &T that is actually &'a T) in the parameters becomes a distinct lifetime parameter.
  • If there is exactly one lifetime used in the parameters (elided or not), that lifetime is assigned to all elided output lifetimes.

In method signatures there is another rule

  • If the receiver has type &Self or &mut Self, then the lifetime of that reference to Self is assigned to all elided output lifetime parameters.

Let's take First::second() as an example. Its signature is:

fn second(&self) -> Second

Or, with all lifetimes explicitly elided (by the way, it is considered a good practice to explictly elide all lifetimes that are not on references, like Second<'_> in this example):

fn second(&'_ self) -> Second<'_>

So according to rule #1 we assign a new lifetime, let's call it 'a, to &self:

fn second<'a>(&'a self) -> Second<'_>

Now, according to rule #3, we pick 'a for Second<'_>:

fn second<'a>(&'a self) -> Second<'a>

That is, we return Second that has the same lifetime as the reference to self.

Now let's apply it to Second::third()...

fn third(&self) -> Third
// Becomes
fn third<'b>(&'b self) -> Third<'b> // Lifetime `'a` is already used

But this is not what we want! We want the resulting Third to depend on the lifetime of our contained First instance, not on the lifetime of &self! So what we really need is to use Third<'a>:

fn third(&self) -> Third<'a> { ... }

Now it works beautifully.

Solution 2:[2]

You just need to specify the same lifetime of the inner reference in Second when creating Third:

impl<'a> Second<'a> {
    fn new(owner: &'a First) -> Self {
        Self { owner }
    }
    fn third(&self) -> Third<'a> {
        Third::new(self.owner)
    }
    fn hello(&self) {
        self.owner.hello();
    }
}

Playground

It is the same as when creating Second from first:

impl First {
...
    fn second(&self) -> Second {
        Second::new(self)
    }
...
}

Here, second is bound to a 'self lifetime, that is, it can live as much as that &self First instance. But in this case the compiler specify it for you.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Chayim Friedman
Solution 2