这个实例看似如何超过其自己的参数生存期? [英] How can this instance seemingly outlive its own parameter lifetime?
问题描述
在我偶然发现下面的代码之前,我确信类型的生命周期参数中的生命周期将永远超过其自身的实例.换句话说,给定foo: Foo<'a>
,那么'a
总是比foo
寿命更长.然后@Luc Danton(游乐场):
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
. Then I was introduced to this counter-argument code by @Luc Danton (Playground):
#[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);
}
即使hint
创建的foo
似乎考虑了寿命不长于其寿命的寿命,并且对该寿命的引用被传递到更广泛的范围的函数中,但代码的编译过程与它完全相同是.取消注释代码中指出的行会触发编译错误.或者,将Foo
更改为结构元组(PhantomData<&'a ()>)
也会使代码不再以相同类型的错误进行编译(
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. 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).
有效的Rust代码如何?这里的编译器是什么原因?
How is it valid Rust code? What is the reasoning of the compiler here?
推荐答案
尽管尽力而为,您的hint
函数可能无法达到预期的效果.但是,在我们了解发生了什么之前,我们有很多基础要覆盖.
Despite your best intentions, your hint
function may not have the effect you expect. But we have quite a bit of ground to cover before we can understand what's going on.
让我们从这里开始:
fn ensure_equal<'z>(a: &'z (), b: &'z ()) {}
fn main() {
let a = ();
let b = ();
ensure_equal(&a, &b);
}
好,因此在main
中,我们定义了两个变量a
和b
.由于有不同的let
语句引入,因此它们具有不同的生存期. ensure_equal
需要两个具有相同生存期的引用.但是,此代码可以编译.为什么?
OK, so in main
, we defining two variables, a
and b
. They have distinct lifetimes, by virtue of being introduced by distinct let
statements. ensure_equal
requires two references with the same lifetime. And yet, this code compiles. Why?
这是因为,给定'a: 'b
(阅读:'a
比'b
长),&'a T
是
That's because, given 'a: 'b
(read: 'a
outlives 'b
), &'a T
is a subtype of &'b T
.
假设a
的生存期为'a
,b
的生存期为'b
. 'a: 'b
是事实,因为首先引入了a
.在调用ensure_equal
时,参数分别键入&'a ()
和&'b ()
1 .这里存在类型不匹配的情况,因为'a
和'b
的生存期不同.但是编译器还没有放弃!它知道&'a ()
是&'b ()
的子类型.换句话说,&'a ()
是 &'b ()
.因此,编译器将强制表达式&a
键入&'b ()
,以便两个参数都键入&'b ()
.这样可以解决类型不匹配的问题.
Let's say the lifetime of a
is 'a
and the lifetime of b
is 'b
. It's a fact that 'a: 'b
, because a
is introduced first. On the call to ensure_equal
, the arguments are typed &'a ()
and &'b ()
, respectively1. There's a type mismatch here, because 'a
and 'b
are not the same lifetime. But the compiler doesn't give up yet! It knows that &'a ()
is a subtype of &'b ()
. In other words, a &'a ()
is a &'b ()
. The compiler will therefore coerce the expression &a
to type &'b ()
, so that both arguments are typed &'b ()
. This resolves the type mismatch.
如果您对带有生命周期的子类型"的应用感到困惑,那么让我用Java术语重新说明该示例.让我们用Programmer
替换&'a ()
,用Person
替换&'b ()
.现在让我们说Programmer
是从Person
派生的:因此Programmer
是Person
的子类型.这意味着我们可以采用类型为Programmer
的变量,并将其作为参数传递给需要参数类型为Person
的函数.这就是以下代码将成功编译的原因:对于main
中的调用,编译器会将T
解析为Person
.
If you're confused by the application of "subtypes" with lifetimes, then let me rephrase this example in Java terms. Let's replace &'a ()
with Programmer
and &'b ()
with Person
. Now let's say that Programmer
is derived from Person
: Programmer
is therefore a subtype of 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
. That's why the following code will successfully compile: the compiler will resolve T
as Person
for the call in main
.
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);
}
}
此子类型关系的非直观方面可能是,较长的寿命是较短寿命的子类型.但是请这样想:在Java中,可以安全地假设Programmer
是Person
,但是您不能假定Person
是Programmer
.同样,可以假装一个变量的寿命较短,但是您不能假定某个已知寿命的变量寿命较长.毕竟,Rust的整个生命周期都是为了确保您不能访问超出其实际生命周期的对象.
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
. 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.
现在,让我们谈谈差异.那是什么?
Now, let's talk about variance. What's that?
方差是类型构造函数在其参数方面具有的属性. Rust中的类型构造函数是带有未绑定参数的泛型类型.例如,
Vec
是一个类型构造函数,它使用T
并返回Vec<T>
.&
和&mut
是类型构造函数,需要两个输入:生存期和指向的类型.
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. For instance
Vec
is a type constructor that takes aT
and returns aVec<T>
.&
and&mut
are type constructors that take two inputs: a lifetime, and a type to point to.
通常,您希望Vec<T>
的所有元素都具有相同的类型(并且我们这里不讨论特征对象).但是差异让我们以此作弊.
Normally, you would expect all elements of a Vec<T>
to have the same type (and we're not talking about trait objects here). But variance lets us cheat with that.
&'a T
在'a
和T
上是协变.这意味着,只要在类型参数中看到&'a T
的地方,都可以用&'a T
的子类型替换它.让我们看看它是如何工作的:
&'a T
is covariant over 'a
and T
. That means that wherever we see &'a T
in a type argument, we can substitute it with a subtype of &'a T
. Let's see how it works out:
fn main() {
let a = ();
let b = ();
let v = vec![&a, &b];
}
我们已经确定a
和b
具有不同的生存期,并且表达式&a
和&b
具有不同的类型 1 .那么为什么我们可以用这些来制作Vec
呢?推理与上面相同,因此我总结一下:&a
被强制为&'b ()
,因此v
的类型为Vec<&'b ()>
.
We've already established that a
and b
have different lifetimes, and that the expressions &a
and &b
don't have the same type1. So why can we make a Vec
out of these? 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 ()>
.
fn(T)
在Rust中是一种特殊情况,涉及方差. fn(T)
与T
相比是相反的.让我们构建一个Vec
函数!
fn(T)
is a special case in Rust when it comes to variance. fn(T)
is contravariant over T
. Let's build a Vec
of functions!
fn foo(_: &'static ()) {}
fn bar<'a>(_: &'a ()) {}
fn quux<'a>() {
let v = vec![
foo as fn(&'static ()),
bar as fn(&'a ()),
];
}
fn main() {
quux();
}
这会编译.但是quux
中的v
是什么类型?是Vec<fn(&'static ())>
还是Vec<fn(&'a ())>
?
This compiles. But what's the type of v
in quux
? Is it Vec<fn(&'static ())>
or Vec<fn(&'a ())>
?
我会给你一个提示:
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
我们正在尝试使用&'a ()
参数调用向量中的函数之一.但是v[0]
需要一个&'static ()
,并且不能保证'a
是'static
,因此这是无效的.因此,我们可以得出结论,v
的类型是Vec<fn(&'static ())>
.如您所见,协方差与协方差相反:我们可以用 long 代替短寿命.
We're trying to call one of the functions in the vector with a &'a ()
argument. But v[0]
expects a &'static ()
, and there's no guarantee that 'a
is 'static
, so this is invalid. We can therefore conclude that the type of v
is Vec<fn(&'static ())>
. As you can see, contravariance is the opposite of covariance: we can replace a short lifetime with a longer one.
现在,回到您的问题.首先,让我们看看编译器从hint
调用中得出的结果. hint
具有以下签名:
Whew, now back to your question. First, let's see what the compiler makes out of the call to hint
. hint
has the following signature:
fn hint<'a, Arg>(_: &'a Arg) -> Foo<'a>
Foo
与'a
相比是相反的,因为Foo
包装了fn
(或者,由于PhantomData
,假装为),但是当我们谈论方差时没有什么区别;两者都具有相同的效果),fn(T)
与T
是相反的,而T
在这里是&'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 ()
.
当编译器尝试解析对hint
的调用时,它仅考虑shortlived
的生存期.因此,hint
返回具有shortlived
生存期的Foo
.但是,当我们尝试将其分配给变量foo
时,我们遇到了一个问题:类型上的生存期参数总是超过类型本身,并且shortlived
的生存期不超过foo
的生存期,因此显然,我们不能在foo
中使用该类型.如果Foo
与'a
协变,那将是结尾,并且您会得到一个错误.但是Foo
与'a
相比是相反的,因此我们可以将shortlived
的生存期替换为更大的生存期.该生存期可以是超过foo
生存期的任何生存期.请注意,生存期"与严格生存期"不同:区别在于'a: 'a
('a
生存期'a
)是true,但是'a
严格生存期'a
是false(即生存期是表示自己的生命力很强,但并不能严格地生命力本身).因此,我们可能最终得到类型为Foo<'a>
的foo
,其中'a
恰好是foo
本身的生存期.
When the compiler tries to resolve the call to hint
, it only considers shortlived
's lifetime. Therefore, hint
returns a Foo
with shortlived
's lifetime. 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
. If Foo
was covariant over 'a
, that would be the end of it and you'd get an error. But Foo
is contravariant over 'a
, so we can replace shortlived
's lifetime with a larger lifetime. That lifetime can be any lifetime that outlives foo
's lifetime. 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 (i.e. a lifetime is said to outlive itself, but it doesn't strictly outlive itself). Therefore, we might end up with foo
having type Foo<'a>
where 'a
is exactly the lifetime of foo
itself.
现在让我们看一下check(&foo, &outlived);
(这是第二个).之所以进行编译,是因为&outlived
被强制执行,从而缩短了生存期以匹配foo
的生存期.之所以成立是因为outlived
的生存期比foo
长,并且check
的第二个参数是'a
的协变变量,因为它是引用.
Now let's look at 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. 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.
为什么check(&foo, &shortlived);
无法编译? foo
的寿命比&shortlived
的寿命长. check
的第二个参数是'a
的协变,但它的第一个参数是'a
的 contravariant ,因为Foo<'a>
是协变的.也就是说,两个参数都试图朝相反的方向拉'a
来进行此调用:&foo
试图延长&shortlived
的寿命(这是非法的),而&shortlived
试图缩短&foo
'.的生命(也是非法的).没有生存期可以统一这两个变量,因此调用无效.
Why doesn't check(&foo, &shortlived);
compile? foo
has a longer lifetime than &shortlived
. check
's second argument is covariant over 'a
, but its first argument is contravariant over 'a
, because Foo<'a>
is contravariant. 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). There is no lifetime that will unify these two variables, therefore the call is invalid.
1 可能实际上是一种简化.我相信参考的有效期参数实际上代表借用处于活动状态的区域,而不是参考的有效期.在此示例中,两个借用对于包含对ensure_equal
的调用的语句都是有效的,因此它们将具有相同的类型.但是,如果将借用拆分为单独的let
语句,该代码仍然有效,因此说明仍然有效.就是说,要使借阅有效,推荐对象必须超过借用区域,因此,当我考虑生命周期参数时,我只关心引用对象的生命周期,并且考虑单独借用.
1 That might actually be a simplification. 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. But if you split the borrows to separate let
statements, the code still works, so the explanation is still valid. 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.
这篇关于这个实例看似如何超过其自己的参数生存期?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!