gpt4 book ai didi

rust - 为什么作为参数传递的特征对象的生存期需要较高的特质界限,而结构却不需要?

转载 作者:行者123 更新时间:2023-12-03 11:38:07 25 4
gpt4 key购买 nike

将特征对象传递给函数时,如何处理生存期?

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/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com