将特征对象传递给函数时,如何处理生存期?
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对象的引用。
我读到了这是进入更高特质界限的地方。将{em> foo2 修改为foo2(t: &for<'a> Spinner<&'a i32>)
可以编译代码,但是我不明白为什么。
为什么'a
不会缩水x
?
引用the Nomicon:
我们到底该如何表达
F
的特质界限上的生命?我们需要在此处提供一些生命周期,但是直到我们进入调用主体之前,我们才能指定我们关心的生命周期!而且,这不是固定的寿命;call
可以在任何生命周期内&self
使用。
请详细说明吗?
答案 0 :(得分:8)
简而言之::foo1
会进行编译,因为大多数类型在其通用参数上有所不同,并且编译器仍可以为Spinner
选择一个t
隐含形式。 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。
换句话说:我们知道我们有 something 可以处理寿命至少为'a
的引用。但是我们不能认为我们所处理的东西还可以处理比'a
更短的生命!
例如:
struct Star;
impl Spinner<&'static i32> for Star {
fn spin(&self, value: &'static i32) {}
}
static SUN: Star = Star;
foo2(&SUN);
在此示例中,'a
中的foo2
是'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已经修复!与特征对象一样,我们只知道S
实现了Spinner<&'a i32>
,而不一定实现更多。
使用排名较高的特征范围可以解决此问题:
fn foo2(t: &for<'a> Spinner<&'a i32>)
和
fn foo4<S: for<'a> Spinner<&'a i32>>(t: &S)
从上面的解释中可以很明显地看出来,这是可行的,因为我们不再固定Spinner
的具体含义!取而代之的是,我们又有无限多个提示可供选择(每个'a
一个)。因此,我们可以在'a == 'x
处选择隐含符号。