尽管你的初衷是好的,但你的hint
功能可能没有达到你预期的效果.但在我们了解发生了什么之前,我们还有很多事情要做.
让我们从这个开始:
fn ensure_equal<'z>(a: &'z (), b: &'z ()) {}
fn main() {
let a = ();
let b = ();
ensure_equal(&a, &b);
}
在main
中,我们定义了两个变量,a
和b
.它们有不同的生命周期,因为它们由不同的let
条语句引入.ensure_equal
需要两个参考文献和the same lifetime.然而,这段代码是可以编译的.为什么?
这是因为,给定'a: 'b
('a
比'b
长寿),&'a T
是&'b T
中的subtype.
假设a
的生命周期 是'a
,b
的生命周期 是'b
.'a: 'b
是事实,因为a
是首先引入的.调用ensure_equal
时,参数分别键入&'a ()
和&'b ()
1.这里有一个类型不匹配,因为'a
和'b
不是同一个生命周期.但是编译器还没有放弃!它知道&'a ()
是&'b ()
的一个亚型.换句话说,一个&'a ()
is a &'b ()
.因此,编译器将强制表达式&a
的类型为&'b ()
,这样两个参数的类型都为&'b ()
.这解决了类型不匹配的问题.
如果你对"子类型"的应用和生命周期感到困惑,那么让我用Java术语重新表述这个例子.让我们用Programmer
替换&'a ()
,用Person
替换&'b ()
.现在让我们假设Programmer
源于Person
:Programmer
是Person
的一个亚型.这意味着我们可以获取Programmer
类型的变量,并将其作为参数传递给需要Person
类型参数的函数.这就是为什么下面的代码将成功编译:对于main
中的调用,编译器将T
解析为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);
}
}
也许这种子类型关系的非直观方面是,较长的生命周期 是较短生命周期 的一个子类型.但可以这样想:在Java中,假装Programmer
是Person
是安全的,但不能假设Person
是Programmer
.同样,可以假设一个变量有shorter个生命周期,但不能假设一个已知生命周期的变量实际上有longer个生命周期.毕竟,Rust的整个使用生命周期 是为了确保在实际使用生命周期 之外不会接触到对象.
现在,让我们谈谈variance.那是什么?
方差是类型构造函数对其参数具有的属性.Rust中的类型构造函数是具有未绑定参数的泛型类型.例如,Vec
是一个类型构造函数,它接受T
并返回Vec<T>
.&
和&mut
是接受两个输入的类型构造函数:生存期和指向的类型.
通常情况下,你会期望Vec<T>
的所有元素都具有相同的类型(这里我们不是在讨论trait对象).但变异让我们可以用它作弊.
&'a T
等于covariant除以'a
和T
.这意味着,只要我们在类型参数中看到&'a T
,我们就可以用&'a T
的子类型替换它.让我们看看结果如何:
fn main() {
let a = ();
let b = ();
let v = vec![&a, &b];
}
我们已经确定a
和b
有不同的生存期,表达式&a
和&b
没有相同的类型1.那么为什么我们能用这些做一个Vec
呢?推理与上面相同,因此我将总结:&a
被强制为&'b ()
,因此v
的类型是Vec<&'b ()>
.
fn(T)
是Rust 的特例.fn(T)
是contravariant比T
.让我们构建一个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();
}
这是一本汇编.但是quux
中的v
是什么类型呢?是Vec<fn(&'static ())>
还是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(&());
}
这是doesn't美元.以下是编译器消息:
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 ())>
.正如你所看到的,反方差与协方差相反:我们可以用longer年的生命周期 来代替短暂的生命周期 .
喂,现在回到你的问题上来.首先,让我们看看编译器如何利用对hint
的调用.hint
有以下签名:
fn hint<'a, Arg>(_: &'a Arg) -> Foo<'a>
Foo
代表contravariant比'a
,因为Foo
代表fn
(或者更确切地说,pretends to,多亏了PhantomData
,但当我们谈论方差时,这没有什么区别;两者都有相同的效果),fn(T)
代表T
的逆变,这里的T
代表&'a ()
.
当编译器试图将调用解析为hint
时,它只考虑shortlived
的生存期.因此,hint
返回Foo
和shortlived
的生命周期 .但当我们试图将其分配给变量foo
时,我们遇到了一个问题:类型上的生存期参数总是比类型本身的生存期长,而shortlived
的生存期并不比foo
的生存期长,所以很明显,我们不能将该类型用于foo
.如果Foo
的协变系数大于'a
,那么就结束了,你会得到一个错误.但是Foo
是contravariant除以'a
,所以我们可以用larger代替shortlived
的生命周期 .这一生可以是任何超过foo
岁的一生.请注意,"超过生命周期 "与"严格超过生命周期 "不同:区别在于'a: 'a
('a
超过'a
)是真的,但'a
严格超过'a
是假的(也就是说,一个人的一生据说比自己活得长,但它本身并不是strictly outlive).因此,我们可能最终得到foo
具有Foo<'a>
型,其中'a
正好是foo
本身的生命周期 .
现在让我们看看check(&foo, &outlived);
(这是第二个).这一个编译是因为&outlived
被强制,所以生命周期被缩短,以匹配foo
的生命周期.这是有效的,因为outlived
的生命周期 比foo
长,而check
的第二个参数是'a
的协变参数,因为它是一个引用.
为什么check(&foo, &shortlived);
不能编译?foo
的生命周期 比&shortlived
长.check
的第二个参数是'a
的协变,但它的第一个参数是contravariant的'a
,因为Foo<'a>
是逆变的.也就是说,这两个论点都试图将'a
拉向这个调用的相反方向:&foo
试图扩大&shortlived
的生命周期 (这是非法的),而&shortlived
试图缩短&foo
的生命周期 (这也是非法的).没有将这两个变量统一起来的生存期,因此调用无效.
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 100, so they would have the same type. But if you split the borrows to separate 101 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.