为什么作为参数传递的特征对象的生存期需要较高的特质界线,而结构却不需要呢? [英] Why do the lifetimes on a trait object passed as an argument require Higher Ranked Trait Bounds but a struct doesn't?

查看:61
本文介绍了为什么作为参数传递的特征对象的生存期需要较高的特质界线,而结构却不需要呢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

How are lifetimes handled when there is a trait object passed to a function?

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);
}

(游乐场)

此代码导致此错误:

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对象的引用.

The function signature of foo1 is nearly same as foo2. One receiving the reference to struct and the other a trait object.

我读到了这是进入更高特质界限的地方.将 foo2 修改为foo2(t: &for<'a> Spinner<&'a i32>)可以编译代码,但是我不明白为什么.

I read this is where Higher Ranked Trait Bounds comes in. Modifying foo2 as foo2(t: &for<'a> Spinner<&'a i32>) compiles the code, but I don't understand why.

为什么'a不能缩小x?

引用 Nomicon :

我们到底该如何表达F特质界的生命?我们需要在此处提供一些生命周期,但是直到我们进入调用主体之前,我们才能指定我们关心的生命周期!而且,这不是固定的寿命. call可以在任何生命周期使用&self恰好在那个时候.

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已经固定.

In short: foo1 compiles because most types are variant over their generic parameters and the compiler can still chose a Spinner impl for t. foo2 doesn't compile because traits are invariant over their generic parameters and the Spinner impl is already fixed.

让我们看一下foo的第三个版本:

Let's take a look at a third version of foo:

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

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

This results in the same error as your foo2. What's going in there?

通过编写Spinner::<&'a i32>::spin,我们强制编译器使用Spinner特征的特定实现. Spinner::<&'a i32>::spin的签名是fn spin(&self, value: &'a i32).时期.生存期'a由调用方给定; foo无法选择它.因此,我们必须传递一个至少存在'a的引用.这就是为什么发生编译器错误.

By writing Spinner::<&'a i32>::spin, we force the compiler to use a specific implementation of the Spinner trait. And the signature of Spinner::<&'a i32>::spin is fn spin(&self, value: &'a i32). Period. The lifetime 'a is given by the caller; foo can't choose it. Thus we have to pass a reference that lives for at least 'a. That's why the compiler error happens.

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

So why does foo1 compile? As a reminder:

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的特定生存期)!

Here, the lifetime 'a is also given by the caller and cannot be chosen by foo1. But, foo1 can chose which impl of Spinner to use! Note that impl<T> Spinner<T> for Planet<T> basically defines infinitely many specific implementations (one for each T). So the compiler also knows that Planet<&'x i32> does implement Spinner<&'x i32> (where 'x is the specific lifetime of x in the function)!

现在,编译器只需要弄清楚是否可以将Planet<&'a i32>转换为Planet<&'x i32>.是的,它可以,因为大多数类型在其通用参数上都有差异,因此如果'a'x的子类型,则Planet<&'a i32>Planet<&'x i32>的子类型.因此,编译器只需将t转换为Planet<&'x i32>,然后就可以使用Spinner<&'x i32> impl.

Now the compiler just has to figure out if it can turn Planet<&'a i32> into Planet<&'x i32>. And yes, it can, because most types are variant over their generic parameters and thus Planet<&'a i32> is a subtype of Planet<&'x i32> if 'a is a subtype of 'x (which it is). So the compiler just "converts" t to Planet<&'x i32> and then the Spinner<&'x i32> impl can be used.

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

Fantastic! But now to the main part: why doesn't foo2 compile then? Again, as a reminder:

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

同样,'a由调用方提供,而foo2无法选择它.不幸的是,现在我们已经有一个特定的实现!即Spinner<&'a i32>.我们不能仅仅假设我们被传递的东西在其他任何生命周期'o != 'a中也实现了Spinner<&'o i32>特质在其通用参数上是不变的.

Again, 'a is given by the caller and foo2 cannot chose it. Unfortunately, now we already have a specific implementation! Namely Spinner<&'a i32>. We can't just assume that the thing we were passed also implements Spinner<&'o i32> for any other lifetime 'o != 'a! Traits are invariant over their generic parameters.

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

In other words: we know we have something that can handle references which live at least as long as 'a. But we can't assume that the thing we were handed can also handle lifetimes shorter than '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.

In this example, 'a of foo2 is 'static. And in fact, Star implements Spinner only for 'static references to i32.

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

By the way: this is not specific to trait objects! Let's look at this fourth version of 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>,而不一定实现更多.

Same error once again. The problem is, again, that the Spinner impl is already fixed! As with the trait object, we only know that S implements Spinner<&'a i32>, not necessarily more.

使用排名较高的特征范围可以解决此问题:

Using higher ranked trait bounds resolves the issue:

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

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

从上面的解释中可以很清楚地看出,这行得通,是因为我们不再固定Spinner的具体含义!取而代之的是,我们又有无限多个提示可供选择(每个'a一个).因此,我们可以在'a == 'x处选择隐含符号.

As it's hopefully clear from the explanation above, this works because we the specific impl of Spinner isn't fixed anymore! Instead, we again have infinitely many impls to choose from (one for each 'a). Thus we can choose the impl where 'a == 'x.

这篇关于为什么作为参数传递的特征对象的生存期需要较高的特质界线,而结构却不需要呢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆