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 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<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>的子类型(实际上是most types are variant over their generic parameters),因此'a也是'x的子类型。因此,编译器仅将t转换为Planet<&'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的具体含义!取而代之的是,我们又有无限多个提示可供选择(每个'a一个)。因此,我们可以选择impl where 'a == 'x

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

相关文章:

rust - 为什么这个 MutexGuard 没有被丢弃?

rust - 为什么不能在同一结构中存储值和对该值的引用?

reference - 为什么 Rust 需要明确的生命周期?

rust - 如何共享堆分配的特征对象?

debugging - 通过openocd和gdb调试stm32f407时,Rust调试不会在断点处停止

rust - 具有包含异步 block 的闭包的异步方法无法推断适当的生存期

rust - 如何理解 Rust 中函数参数和返回值的生命周期?

static - 为什么在同一范围内可以有多个具有静态生命周期的可变引用

rust - 移至actix-web 3.0时发生错误

rust - 是否有等同于 C++ 中时钟函数的 Rust?