为什么作为参数传递的特征对象的生存期需要较高的特质界线,而结构却不需要呢? [英] Why do the lifetimes on a trait object passed as an argument require Higher Ranked Trait Bounds but a struct doesn't?
问题描述
将特征对象传递给函数时,如何处理生存期?
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
仅针对'static
对i32
的引用实现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屋!