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

标签 rust lifetime

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

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 对象

我读到这是高级特征边界的用武之地。将 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编译是因为大多数类型的泛型参数都是变体,编译器仍然可以选择 Spinner用于 t 的实现. 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<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 的子类型(它是)。所以编译器只是“转换”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 != 'a ! Traits 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);

在这个例子中,'afoo2'static .事实上,Star工具 Spinner仅适用于 'static引用 i32 .


顺便说一下:这不是特征对象特有的!让我们看看 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 .

关于rust - 为什么作为参数传递的特征对象的生命周期需要更高等级的特征界限,而结构不需要?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50946525/

相关文章:

reference - 借用的值在循环中的生命周期不够长

string - 在HashMap中将char用作&str

module - 在单独的文件中定义特征

types - 克隆特性 BufRead 的行时类型不匹配

rust - 为什么循环中的不可变借用会超出其词法范围?

javascript - 有没有办法从 WebAssembly 上下文写入 JavaScript 缓冲区?

winapi - 如何使用 Rust 和 winapi crate 在 Windows 中创建事件订阅?

static - 为什么不能将用于内存函数的静态散列图借用为可变的?

android - 在Android中模拟 Activity 死亡

c++ - 异步函数调用的参数生命周期