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

sou*_*ics 10 lifetime rust

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

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);
}
Run Code Online (Sandbox Code Playgroud)

(游乐场)

此代码导致此错误:

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>) {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

功能签名foo1几乎相同foo2.一个接收对struct的引用,另一个接受trait对象.

我看这是在更高等级的特质界的用武之地.修改foo2的foo2(t: &for<'a> Spinner<&'a i32>)编译的代码,但我不明白为什么.

为什么不'a缩小x

引用Nomicon:

我们究竟应该如何表达生命F的特征?我们需要在那里提供一些生命,但是我们关心的生命在我们进入呼叫体之前无法命名!而且,这不是一些固定的生命周期; call适用于任何生命&self在这一点上.

请详细说明一下吗?

Luk*_*odt 8

简而言之: foo1编译因为大多数类型都是通用参数的变体,编译器仍然可以选择Spinnerimpl t.foo2不编译,因为traits对其泛型参数不变,并且Spinnerimpl已经修复.


一些解释

我们来看看第三个版本foo:

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

这导致与您的错误相同的错误foo2.那里发生了什么?

通过编写Spinner::<&'a i32>::spin,我们强制编译器使用特征的特定实现Spinner.并且签名Spinner::<&'a i32>::spinfn spin(&self, value: &'a i32).期.终身'a由呼叫者给出; foo不能选择它.因此,我们必须传递一个至少存在的参考'a.这就是编译器错误发生的原因.


那么为什么foo1编译?提醒一句:

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

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

现在编译器必须弄清楚它是否可以Planet<&'a i32>变成Planet<&'x i32>.是的,它可以,因为大多数类型都是它们的泛型参数的变体,因此Planet<&'a i32>Planet<&'x i32>if 'a的子类型'x(它是)的子类型.所以编译器只是"转换" tPlanet<&'x i32>然后Spinner<&'x i32>可以使用impl.


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

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

同样,'a由呼叫者给出并且foo2不能选择它.不幸的是,现在我们已经有了具体的实现!即Spinner<&'a i32>.我们不能只假设我们传递的东西也Spinner<&'o i32>适用于任何其他生命周期'o != 'a!特征对其通用参数不变.

换句话说:我们知道我们有一些东西可以处理至少和它们一样长的参考文献'a.但我们不能假设我们所交的东西也可以处理比生命更短的寿命'a!

举个例子:

struct Star;

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

static SUN: Star = Star;

foo2(&SUN);
Run Code Online (Sandbox Code Playgroud)

在这个例子中,'afoo2'static.而事实上,Star实现Spinner仅供'static参考文献i32.


顺便说一下:这不是特质对象!让我们来看看第四个版本foo:

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

同样的错误再一次.问题是,Spinnerimpl已经修复了!与特征对象一样,我们只知道S实现Spinner<&'a i32>,不一定更多.

HRTB救援?

使用更高排名的特质边界可以解决问题:

fn foo2(t: &for<'a> Spinner<&'a i32>)
Run Code Online (Sandbox Code Playgroud)

fn foo4<S: for<'a> Spinner<&'a i32>>(t: &S)
Run Code Online (Sandbox Code Playgroud)

正如从上面的解释中可以清楚地看到的那样,这是有效的,因为我们的特定impl Spinner不再是固定的!相反,我们再次拥有无限多的投注可供选择(每个投注一个'a).因此我们可以选择impl在哪里'a == 'x.