如何使从对象到其孙子的生命周期约束 "pass through"?

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

我正在对生命周期和借用检查器进行一些实验。想象一下第一个结构:

struct First {}

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

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

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

第二个,其生命周期限制取决于 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();
    }
}

上面的代码工作得很好:Second 是由 First 创建的,它不能超过 First

问题

现在让我们修改 Second 以便它可以创建第三个结构,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();
    }
}

Third本身,这也取决于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();
    }
}

我想,在创建 Third 的实例时,它会依赖于 First,但事实并非如此。实际上 Third 取决于 Second:

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

那么,我怎样才能使生命周期约束从 First“通过”到 Third

Full playground.

创建Third时,只需要在Second中指定与内部引用相同的生命周期即可:

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

和第一个创建Second时一样:

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

在这里,second 被绑定到 'self 生命周期,也就是说,它可以与 &self First 实例一样长。但在这种情况下,编译器会为您指定它。

Rust 有如何推断函数生命周期的规则:lifetime elision rules.

这些规则规定:

  • 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.

我们以First::second()为例。它的签名是:

fn second(&self) -> Second

或者,显式省略所有生命周期(顺便说一下,显式省略所有不在引用上的生命周期被认为是一种很好的做法,例如本例中的 Second<'_>):

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

所以根据规则 #1 我们分配一个新的生命周期,我们称它为 'a,到 &self:

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

现在,根据规则 #3,我们为 Second<'_> 选择 'a:

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

也就是说,我们 return Second 与对 self 的引用具有相同的生命周期。

现在让我们将它应用到 Second::third()...

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

但这不是我们想要的!我们希望生成的 Third 取决于我们包含的 First 实例的生命周期,而不是 &self 的生命周期!所以我们真正需要的是使用 Third<'a>:

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

现在it works beautifully.