'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?
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
&Tthat 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
&Selfor&mut Self, then the lifetime of that reference toSelfis 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();
}
}
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 |
