为什么作为参数传递的 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?
问题描述
当将 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<&'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>
.是的,它可以,因为 大多数类型都是泛型参数的变体,因此Planet<&'a i32>
是 Planet<&'x i32>
的子类型,如果 'a
是 的子类型>'x
(它是).所以编译器只是转换" 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>
.我们不能仅仅假设我们传递的东西也为任何其他生命周期实现了 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屋!