[英]How can this instance seemingly outlive its own parameter lifetime?
Before I stumbled upon the code below, I was convinced that a lifetime in a type's lifetime parameter would always outlive its own instances. 在我偶然发现下面的代码之前,我确信类型的生命周期参数中的生命周期总是比其自己的实例更长。 In other words, given a
foo: Foo<'a>
, then 'a
would always outlive foo
. 换句话说,给定一个
foo: Foo<'a>
,那么'a
总是比foo
更长寿。 Then I was introduced to this counter-argument code by @Luc Danton ( Playground ): 然后我被@Luc Danton( 游乐场 )介绍给这个反辩论代码:
#[derive(Debug)]
struct Foo<'a>(std::marker::PhantomData<fn(&'a ())>);
fn hint<'a, Arg>(_: &'a Arg) -> Foo<'a> {
Foo(std::marker::PhantomData)
}
fn check<'a>(_: &Foo<'a>, _: &'a ()) {}
fn main() {
let outlived = ();
let foo;
{
let shortlived = ();
foo = hint(&shortlived);
// error: `shortlived` does not live long enough
//check(&foo, &shortlived);
}
check(&foo, &outlived);
}
Even though the foo
created by hint
appears to consider a lifetime that does not live for as long as itself, and a reference to it is passed to a function in a wider scope, the code compiles exactly as it is. 尽管由
hint
创建的foo
似乎认为生命周期与其自身一样长,并且对它的引用被传递给更广泛范围内的函数,但代码完全按原样编译。 Uncommenting the line stated in the code triggers a compilation error. 取消注释代码中声明的行会触发编译错误。 Alternatively, changing
Foo
to the struct tuple (PhantomData<&'a ()>)
also makes the code no longer compile with the same kind of error ( Playground ). 或者,将
Foo
更改为struct tuple (PhantomData<&'a ()>)
也会使代码不再使用相同类型的错误( Playground )进行编译。
How is it valid Rust code? 如何有效的Rust代码? What is the reasoning of the compiler here?
这里编译器的原因是什么?
Despite your best intentions, your hint
function may not have the effect you expect. 尽管你有最好的意图,你的
hint
功能可能没有你期望的效果。 But we have quite a bit of ground to cover before we can understand what's going on. 但是,在我们了解正在发生的事情之前,我们还有很多理由可以解决。
Let's begin with this: 让我们从这开始:
fn ensure_equal<'z>(a: &'z (), b: &'z ()) {}
fn main() {
let a = ();
let b = ();
ensure_equal(&a, &b);
}
OK, so in main
, we defining two variables, a
and b
. 好的,所以在
main
,我们定义了两个变量a
和b
。 They have distinct lifetimes, by virtue of being introduced by distinct let
statements. 由于被不同的
let
语句引入,它们具有不同的生命周期。 ensure_equal
requires two references with the same lifetime . ensure_equal
需要两个具有相同生命周期的引用。 And yet, this code compiles. 然而,这段代码编译。 Why?
为什么?
That's because, given 'a: 'b
(read: 'a
outlives 'b
), &'a T
is a subtype of &'b T
. 这是因为,给定的
'a: 'b
(读: 'a
会超越'b
&'a T
是一个亚型的&'b T
Let's say the lifetime of a
is 'a
and the lifetime of b
is 'b
. 让我们说的寿命
a
是'a
和寿命b
是'b
。 It's a fact that 'a: 'b
, because a
is introduced first. 这是
'a: 'b
的事实,因为a
首先被引入。 On the call to ensure_equal
, the arguments are typed &'a ()
and &'b ()
, respectively 1 . 在调用
ensure_equal
,参数的类型&'a ()
和&'b ()
,分别为1 。 There's a type mismatch here, because 'a
and 'b
are not the same lifetime. 这里存在类型不匹配,因为
'a
和'b
的生命周期不同。 But the compiler doesn't give up yet! 但是编译器还没有放弃! It knows that
&'a ()
is a subtype of &'b ()
. 它知道
&'a ()
是&'b ()
的子类型。 In other words, a &'a ()
is a &'b ()
. 换句话说,
&'a ()
是 &'b ()
。 The compiler will therefore coerce the expression &a
to type &'b ()
, so that both arguments are typed &'b ()
. 因此编译器将强制表达式
&a
来键入&'b ()
,以便两个参数都被键入&'b ()
。 This resolves the type mismatch. 这解决了类型不匹配的问题。
If you're confused by the application of "subtypes" with lifetimes, then let me rephrase this example in Java terms. 如果你对“子类型”的生命周期的应用感到困惑,那么让我用Java术语来重述这个例子。 Let's replace
&'a ()
with Programmer
and &'b ()
with Person
. 让我们更换
&'a ()
与Programmer
和&'b ()
与Person
。 Now let's say that Programmer
is derived from Person
: Programmer
is therefore a subtype of Person
. 现在让我们说
Programmer
派生自Person
: Programmer
因此是Person
的子类型。 That means that we can take a variable of type Programmer
and pass it as an argument to a function that expects a parameter of type Person
. 这意味着我们可以获取
Programmer
类型的变量,并将其作为参数传递给期望类型为Person
的参数的函数。 That's why the following code will successfully compile: the compiler will resolve T
as Person
for the call in main
. 这就是为什么以下代码将成功编译的原因:编译器将解析
T
作为main
的调用的Person
。
class Person {}
class Programmer extends Person {}
class Main {
private static <T> void ensureSameType(T a, T b) {}
public static void main(String[] args) {
Programmer a = null;
Person b = null;
ensureSameType(a, b);
}
}
Perhaps the non-intuitive aspect of this subtyping relation is that the longer lifetime is a subtype of the shorter lifetime. 也许这种子类型关系的非直观方面是较长的寿命是较短寿命的子类型。 But think of it this way: in Java, it's safe to pretend that a
Programmer
is a Person
, but you can't assume that a Person
is a Programmer
. 但是可以这样想:在Java中,假装
Programmer
是一个Person
是安全的,但你不能假设一个Person
是一个Programmer
。 Likewise, it's safe to pretend that a variable has a shorter lifetime, but you can't assume that a variable with some known lifetime actually has a longer lifetime. 同样,假设变量的生命周期较短是安全的,但您不能假设具有某些已知生命周期的变量实际上具有更长的生命周期。 After all, the whole point of lifetimes in Rust is to ensure that you don't access objects beyond their actual lifetime.
毕竟,Rust中的整个生命周期都是为了确保您不会访问超出实际生命周期的对象。
Now, let's talk about variance . 现在,我们来谈谈差异 。 What's that?
那是什么?
Variance is a property that type constructors have with respect to their arguments.
方差是类型构造函数关于其参数的属性。 A type constructor in Rust is a generic type with unbound arguments.
Rust中的类型构造函数是具有未绑定参数的泛型类型。 For instance
Vec
is a type constructor that takes aT
and returns aVec<T>
.例如,
Vec
是一个类型构造函数,它接受一个T
并返回一个Vec<T>
。&
and&mut
are type constructors that take two inputs: a lifetime, and a type to point to.&
and&mut
是带有两个输入的类型构造函数:生命周期和指向的类型。
Normally, you would expect all elements of a Vec<T>
to have the same type (and we're not talking about trait objects here). 通常,您会期望
Vec<T>
所有元素具有相同的类型(我们在这里不讨论特征对象)。 But variance lets us cheat with that. 但是差异让我们为此作弊。
&'a T
is covariant over 'a
and T
. &'a T
对'a
和T
'a
是协变的 。 That means that wherever we see &'a T
in a type argument, we can substitute it with a subtype of &'a T
. 这意味着无论我们在哪里看到
&'a T
类型参数中&'a T
,我们都可以用&'a T
的子类型替换它。 Let's see how it works out: 让我们看看它是如何工作的:
fn main() {
let a = ();
let b = ();
let v = vec![&a, &b];
}
We've already established that a
and b
have different lifetimes, and that the expressions &a
and &b
don't have the same type 1 . 我们已经确定
a
和b
具有不同的生命周期,并且表达式&a
和&b
不具有相同的类型1 。 So why can we make a Vec
out of these? 那么为什么我们可以用这些来制作
Vec
呢? The reasoning is the same as above, so I'll summarize: &a
is coerced to &'b ()
, so that the type of v
is Vec<&'b ()>
. 推理与上面的相同,所以我总结一下:
&a
被强制为&'b ()
,因此v
的类型是Vec<&'b ()>
。
fn(T)
is a special case in Rust when it comes to variance. fn(T)
是Rust中的一个特殊情况。 fn(T)
is contravariant over T
. fn(T)
在T
是逆变的 。 Let's build a Vec
of functions! 让我们建立一个
Vec
函数!
fn foo(_: &'static ()) {}
fn bar<'a>(_: &'a ()) {}
fn quux<'a>() {
let v = vec![
foo as fn(&'static ()),
bar as fn(&'a ()),
];
}
fn main() {
quux();
}
This compiles. 这编译。 But what's the type of
v
in quux
? 但是
quux
中v
的类型是什么? Is it Vec<fn(&'static ())>
or Vec<fn(&'a ())>
? 它是
Vec<fn(&'static ())>
还是Vec<fn(&'a ())>
?
I'll give you a hint: 我会给你一个提示:
fn foo(_: &'static ()) {}
fn bar<'a>(_: &'a ()) {}
fn quux<'a>(a: &'a ()) {
let v = vec![
foo as fn(&'static ()),
bar as fn(&'a ()),
];
v[0](a);
}
fn main() {
quux(&());
}
This doesn't compile. 这不编译。 Here are the compiler messages:
以下是编译器消息:
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
--> <anon>:5:13
|
5 | let v = vec![
| _____________^ starting here...
6 | | foo as fn(&'static ()),
7 | | bar as fn(&'a ()),
8 | | ];
| |_____^ ...ending here
|
note: first, the lifetime cannot outlive the lifetime 'a as defined on the body at 4:23...
--> <anon>:4:24
|
4 | fn quux<'a>(a: &'a ()) {
| ________________________^ starting here...
5 | | let v = vec![
6 | | foo as fn(&'static ()),
7 | | bar as fn(&'a ()),
8 | | ];
9 | | v[0](a);
10| | }
| |_^ ...ending here
note: ...so that reference does not outlive borrowed content
--> <anon>:9:10
|
9 | v[0](a);
| ^
= note: but, the lifetime must be valid for the static lifetime...
note: ...so that types are compatible (expected fn(&()), found fn(&'static ()))
--> <anon>:5:13
|
5 | let v = vec![
| _____________^ starting here...
6 | | foo as fn(&'static ()),
7 | | bar as fn(&'a ()),
8 | | ];
| |_____^ ...ending here
= note: this error originates in a macro outside of the current crate
error: aborting due to previous error
We're trying to call one of the functions in the vector with a &'a ()
argument. 我们试图使用
&'a ()
参数调用向量中的一个函数。 But v[0]
expects a &'static ()
, and there's no guarantee that 'a
is 'static
, so this is invalid. 但是
v[0]
期望一个&'static ()
,并且不能保证'a
'static
是'static
,所以这是无效的。 We can therefore conclude that the type of v
is Vec<fn(&'static ())>
. 因此,我们可以得出结论,
v
的类型是Vec<fn(&'static ())>
。 As you can see, contravariance is the opposite of covariance: we can replace a short lifetime with a longer one. 正如您所看到的,逆变是与协方差相反的:我们可以用更长的寿命代替短寿命。
Whew, now back to your question. 哇,现在回到你的问题。 First, let's see what the compiler makes out of the call to
hint
. 首先,让我们看一下编译器对
hint
的调用。 hint
has the following signature: hint
具有以下签名:
fn hint<'a, Arg>(_: &'a Arg) -> Foo<'a>
Foo
is contravariant over 'a
because Foo
wraps a fn
(or rather, pretends to , thanks to the PhantomData
, but that doesn't make a difference when we talk about variance; both have the same effect), fn(T)
is contravariant over T
and that T
here is &'a ()
. Foo
是在逆变 'a
,因为Foo
包装了一个fn
(或者更确切地说, 假装 ,感谢PhantomData
,但这并不有所作为,当我们谈论方差;两者具有相同效果), fn(T)
是逆变在T
和T
这里是&'a ()
。
When the compiler tries to resolve the call to hint
, it only considers shortlived
's lifetime. 当编译器尝试解析对
hint
的调用时,它只考虑shortlived
的生命周期。 Therefore, hint
returns a Foo
with shortlived
's lifetime. 因此,
hint
返回一个具有shortlived
生命周期的Foo
。 But when we try to assign that to the variable foo
, we have a problem: a lifetime parameter on a type always outlives the type itself, and shortlived
's lifetime doesn't outlive foo
's lifetime, so clearly, we can't use that type for foo
. 但是当我们尝试将其分配给变量
foo
,我们遇到了一个问题:类型上的生命周期参数总是比类型本身更长,而shortlived
的生命周期并不比foo
的生命周期长,所以显然,我们不能将该类型用于foo
。 If Foo
was covariant over 'a
, that would be the end of it and you'd get an error. 如果
Foo
对'a
是协变'a
,那将是它的结束而你会得到一个错误。 But Foo
is contravariant over 'a
, so we can replace shortlived
's lifetime with a larger lifetime. 但是
Foo
与'a
相反 ,因此我们可以用更长的寿命来取代shortlived
的生命。 That lifetime can be any lifetime that outlives foo
's lifetime. 这一生可以是超过
foo
一生的任何一生。 Note that "outlives" is not the same as "strictly outlives": the difference is that 'a: 'a
( 'a
outlives 'a
) is true, but 'a
strictly outlives 'a
is false (ie a lifetime is said to outlive itself, but it doesn't strictly outlive itself). 请注意,“会超越”是不一样的“严格会超越”:不同的是,
'a: 'a
( 'a
会超越'a
)是真的,但'a
严格会超越'a
是假的(即寿命来表示比自己活得更久,但它并没有严格超过自己)。 Therefore, we might end up with foo
having type Foo<'a>
where 'a
is exactly the lifetime of foo
itself. 因此,我们可能最终得到
foo
类型为Foo<'a>
,其中'a
恰好是foo
本身的生命周期。
Now let's look at check(&foo, &outlived);
现在让我们来看看
check(&foo, &outlived);
(that's the second one). (那是第二个)。 This one compiles because
&outlived
is coerced so that the lifetime is shortened to match foo
's lifetime. 这个编译是因为并且
&outlived
被强迫,所以缩短了生命周期以匹配foo
的生命周期。 That's valid because outlived
has a longer lifetime than foo
, and check
's second argument is covariant over 'a
because it's a reference. 这是有效的,因为
outlived
的寿命比foo
,而check
的第二个参数是'a
协变,因为它是一个引用。
Why doesn't check(&foo, &shortlived);
为什么不
check(&foo, &shortlived);
compile? 编译?
foo
has a longer lifetime than &shortlived
. foo
的寿命比&shortlived
寿命长。 check
's second argument is covariant over 'a
, but its first argument is contravariant over 'a
, because Foo<'a>
is contravariant. check
的第二个参数是'a
协变量,但它的第一个参数是'a
逆变 ,因为Foo<'a>
是逆变的。 That is, both arguments are trying to pull 'a
in opposite directions for this call: &foo
is trying to enlarge &shortlived
's lifetime (which is illegal), while &shortlived
is trying to shorten &foo
's lifetime (which is also illegal). 也就是说,这两个参数都试图拉
'a
相反的方向对这一呼吁: &foo
正试图扩大&shortlived
的生命周期(这是非法的),而&shortlived
试图缩短&foo
的生命周期(这也是非法的)。 There is no lifetime that will unify these two variables, therefore the call is invalid. 没有生命将统一这两个变量,因此调用无效。
1 That might actually be a simplification. 1这实际上可能是一种简化。 I believe that the lifetime parameter of a reference actually represents the region in which the borrow is active, rather than the lifetime of the reference.
我相信参考的生命周期参数实际上代表借用活动的区域,而不是参考的生命周期。 In this example, both borrows would be active for the statement that contains the call to
ensure_equal
, so they would have the same type. 在此示例中,对于包含对
ensure_equal
的调用的语句,两个借位都将处于活动状态,因此它们将具有相同的类型。 But if you split the borrows to separate let
statements, the code still works, so the explanation is still valid. 但是如果你将借用拆分为单独的
let
语句,代码仍然可以工作,所以解释仍然有效。 That said, for a borrow to be valid, the referent must outlive the borrow's region, so when I'm thinking of lifetime parameters, I only care about the referent's lifetime and I consider borrows separately. 也就是说,为了使借用有效,指示对象必须比借用区域更长,所以当我考虑终身参数时,我只关心指示对象的生命周期,我认为借用是分开的。
Another way of explaining this is to notice that Foo
doesn't actually hold a reference to anything with a lifetime of 'a
. 解释这一点的另一种方法是注意到
Foo
实际上并没有引用任何具有'a
生命周期的东西。 Rather, it holds a function that accepts a reference with lifetime 'a
. 相反,它拥有一个接受带有生命周期
'a
引用的函数。
You can construct this same behaviour with an actual function instead of PhantomData
. 您可以使用实际函数而不是
PhantomData
构造相同的行为。 And you can even call that function: 你甚至可以调用该函数:
struct Foo<'a>(fn(&'a ()));
fn hint<'a, Arg>(_: &'a Arg) -> Foo<'a> {
fn bar<'a, T: Debug>(value: &'a T) {
println!("The value is {:?}", value);
}
Foo(bar)
}
fn main() {
let outlived = ();
let foo;
{
let shortlived = ();
// &shortlived is borrowed by hint() but NOT stored in foo
foo = hint(&shortlived);
}
foo.0(&outlived);
}
As Francis explained in his excellent answer, the type of outlived
is a subtype of the type of shortlived
because its lifetime is longer. 正如弗朗西斯在他的出色回答中所解释的那样,
outlived
的类型是shortlived
类型的子类型,因为它的寿命更长。 Therefore, the function inside foo
can accept it because it can be coerced to shortlived
's (shorter) lifetime. 因此,内部的函数
foo
可以接受,因为它可以被强制转换为shortlived
的(短)的寿命。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.