为什么作为参数传递的 trait 对象的生命周期需要更高的 Ranked Trait Bounds 而结构体不需要? [英] Why do the lifetimes on a trait object passed as an argument require Higher Ranked Trait Bounds but a struct doesn't?

查看:31
本文介绍了为什么作为参数传递的 trait 对象的生命周期需要更高的 Ranked Trait Bounds 而结构体不需要?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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 缩小?

Why won't 'a shrink for x?

引用 Nomicon:

我们到底应该如何表达 F 的 trait bound 的生命周期?我们需要在那里提供一些生命周期,但是我们关心的生命周期在我们进入调用体之前是无法命名的!此外,这不是固定的生命周期;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 可以编译,因为大多数类型都是泛型参数的变体,编译器仍然可以为 t 选择了一个 Spinner impl.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对于 Planet 基本上定义了无限多个特定实现(每个 T 一个).所以编译器也知道 Planet<&'x i32> 确实实现了 Spinner<&'x i32>(其中 'xx 在函数中的具体生命周期)!

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>.是的,它可以,因为 大多数类型都是泛型参数的变体,因此Planet<&'a i32>Planet<&'x i32> 的子类型,如果 'a 的子类型>'x(它是).所以编译器只是转换" tPlanet<&'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>.我们不能仅仅假设我们传递的东西也为任何其他生命周期实现了 Spinner<&'o i32> 'o !='a!特征对其通用参数是不变的.

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.

换句话说:我们知道我们有东西可以处理至少与'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 只为 'static 引用 i32 实现了 Spinner.

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

顺便说一句:这不是 trait 对象特有的!让我们看看 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 一个).因此我们可以选择 impl where '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.

这篇关于为什么作为参数传递的 trait 对象的生命周期需要更高的 Ranked Trait Bounds 而结构体不需要?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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