为什么作为参数传递的特征对象的生命周期需要更高等级的特征界限,而结构不需要?

Why do the lifetimes on a trait object passed as an argument require Higher Ranked Trait Bounds but a struct doesn't?

当有特征对象传递给函数时如何处理生命周期?

struct Planet<T> {
    i: T,
}

trait Spinner<T> {
    fn spin(&self, value: T);
}

impl<T> Spinner<T> for Planet<T> {
    fn spin(&self, value: T) {}
}

// foo2 fails: Due to lifetime of local variable being less than 'a
fn foo2<'a>(t: &'a Spinner<&'a i32>) {
    let x: i32 = 10;
    t.spin(&x);
}

// foo1 passes: But here also the lifetime of local variable is less than 'a?
fn foo1<'a>(t: &'a Planet<&'a i32>) {
    let x: i32 = 10;
    t.spin(&x);
}

(Playground)

此代码导致此错误:

error[E0597]: `x` does not live long enough
  --> src/main.rs:16:17
   |
16 |         t.spin(&x);
   |                 ^ borrowed value does not live long enough
17 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 14:5...
  --> src/main.rs:14:5
   |
14 |     fn foo2<'a>(t: &'a Spinner<&'a i32>) {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

foo1的函数签名与foo2几乎相同。一个接收对 struct 的引用,另一个接收 trait 对象.

我读到这是 Higher Ranked Trait Bounds 出现的地方。将 foo2 修改为 foo2(t: &for<'a> Spinner<&'a i32>) 编译代码,但我不明白为什么。

为什么 'a 不会收缩 x

引用 the Nomicon

How on earth are we supposed to express the lifetimes on F's trait bound? We need to provide some lifetime there, but the lifetime we care about can't be named until we enter the body of call! Also, that isn't some fixed lifetime; call works with any lifetime &self happens to have at that point.

能否详细说明一下?

简而言之: foo1 编译是因为大多数类型在其泛型参数上都是变体,编译器仍然可以为 [=19 选择 Spinner impl =]. foo2 无法编译,因为特征在其通用参数上是不变的,并且 Spinner impl 已经固定。


一些解释

再来看看第三版foo:

fn foo3<'a>(t: &'a Planet<&'a i32>) {
    let x: i32 = 10;
    Spinner::<&'a i32>::spin(t, &x);
}

这会导致与您的 foo2 相同的错误。里面发生了什么?

通过编写 Spinner::<&'a i32>::spin,我们强制编译器使用 Spinner 特性的特定实现。而Spinner::<&'a i32>::spin的签名是fn spin(&self, value: &'a i32)。时期。生命周期'a由调用者给定; foo选不出来。因此我们必须传递一个至少存在 'a 的引用。这就是编译器错误发生的原因。


所以为什么foo1编译?提醒一下:

fn foo1<'a>(t: &'a Planet<&'a i32>) {
    let x: i32 = 10;
    t.spin(&x);
}

这里的生命周期'a也是调用者给定的,不能被foo1选择。 但是foo1可以选择使用Spinner的哪个impl!请注意,impl<T> Spinner<T> for Planet<T> 基本上定义了无限多个具体实现(每个 T 一个)。所以编译器也知道Planet<&'x i32>确实实现了Spinner<&'x i32>(其中'x是函数中x的具体生命周期)!

现在编译器只需要弄清楚它是否可以将 Planet<&'a i32> 变成 Planet<&'x i32>。是的,它可以,因为 most types are variant over their generic parameters 因此 Planet<&'a i32>Planet<&'x i32> 的子类型,如果 'a'x 的子类型(它是)。所以编译器只是 "converts" tPlanet<&'x i32> 然后 Spinner<&'x i32> impl 可以被使用。


太棒了!但现在是主要部分:为什么 foo2 不编译? 再次提醒:

fn foo2<'a>(t: &'a Spinner<&'a i32>) {
    let x: i32 = 10;
    t.spin(&x);
}

同样,'a是调用者给的,foo2不能选择。不幸的是,现在我们已经有了具体的实现!即Spinner<&'a i32>。我们不能假设我们传递的东西也实现了 Spinner<&'o i32> 任何其他生命周期 'o != 'aTraits are invariant over their generic parameters.

换句话说:我们知道我们有 东西 可以处理至少与 'a 一样长的引用。但是我们不能假设我们得到的东西也可以处理短于 'a 的生命周期!

例如:

struct Star;

impl Spinner<&'static i32> for Star {
    fn spin(&self, value: &'static i32) {}
}

static SUN: Star = Star;

foo2(&SUN);

在这个例子中,foo2'a'static。事实上,Star 仅针对 'statici32 的引用实现 Spinner


顺便说一句:这不是特定于特征对象!让我们看看foo的第四个版本:

fn foo4<'a, S: Spinner<&'a i32>>(t: &'a S) {
    let x: i32 = 10;
    t.spin(&x);
}

再次出现同样的错误。问题又是 Spinner impl 已经修复了!至于trait对象,我们只知道S实现了Spinner<&'a i32>,不一定更多

HRTB 来拯救?

使用更高等级的特征界限解决了问题:

fn foo2(t: &for<'a> Spinner<&'a i32>)

fn foo4<S: for<'a> Spinner<&'a i32>>(t: &S)

正如上面的解释所希望的那样,这是可行的,因为我们 Spinner 的具体含义不再固定!相反,我们再次有无限多的 impl 可供选择(每个 'a 一个)。因此我们可以选择 'a == 'x.

的 impl