gpt4 book ai didi

rust - 这个实例看似如何超过其自身的参数生存期?

转载 作者:行者123 更新时间:2023-12-03 11:07:48 26 4
gpt4 key购买 nike

在偶然发现下面的代码之前,我深信类型的生命周期参数中的生命周期将永远超过其自身的实例。换句话说,给定foo: Foo<'a>,那么'a将永远超过foo。然后,@ 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);
}

即使 foo创建的 hint似乎考虑了生命周期不长于其自身的生存期,并且对其的引用被传递给了范围更广的函数,但代码仍按原样进行编译。取消注释代码中指出的行会触发编译错误。另外,将 Foo更改为结构元组 (PhantomData<&'a ()>)也会使代码不再以相同类型的错误( Playground)进行编译。

有效的Rust代码如何?这里的编译器是什么原因?

最佳答案

尽管您的意图最好,但是hint函数可能没有达到您期望的效果。但是,在我们了解发生了什么之前,我们有很多基础要覆盖。

让我们从这个开始:

fn ensure_equal<'z>(a: &'z (), b: &'z ()) {}

fn main() {
let a = ();
let b = ();
ensure_equal(&a, &b);
}

好的,因此在 main中,我们定义了两个变量 ab。由于它们是由不同的 let语句引入的,因此它们具有不同的生存期。 ensure_equal需要两个具有相同生存期的引用。但是,此代码可以编译。为什么?

这是因为,给定 'a: 'b(阅读: 'a超过 'b), &'a T&'b Tsubtype

假设 a的生存期为 'a,而 b的生存期为 'b。这是 'a: 'b的事实,因为首先引入了 a。在调用 ensure_equal时,分别将参数键入 &'a ()&'b ()1。这里存在类型不匹配的情况,因为 'a'b的生存期不同。但是编译器还没有放弃!它知道 &'a ()&'b ()的子类型。换句话说, &'a ()&'b ()。因此,编译器将强制表达式 &a键入 &'b (),以便两个参数都键入 &'b ()。这样可以解决类型不匹配的问题。

如果您对带有生命周期的“子类型”应用程序感到困惑,那么让我用Java术语重新说明该示例。让我们用 &'a ()替换 Programmer,用 &'b ()替换 Person。现在让我们说 Programmer是从 Person派生的: Programmer因此是 Person的子类型。这意味着我们可以采用 Programmer类型的变量,并将其作为参数传递给需要 Person类型的参数的函数。这就是以下代码将成功编译的原因:编译器会将 T解析为 Person,以用于 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中,可以假装 ProgrammerPerson是安全的,但是您不能假定 PersonProgrammer。同样,可以安全地假设变量的生存期较短,但是您不能假设某个已知生存期的变量实际上具有更长的生存期。毕竟,Rust的整个生命周期都是为了确保您不能访问超出其实际生命周期的对象。

现在,让我们谈谈 variance。那是什么?

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 a T and returns a Vec<T>. & and &mut are type constructors that take two inputs: a lifetime, and a type to point to.



通常,您会期望 Vec<T>的所有元素都具有相同的类型(并且我们此处不讨论特征对象)。但是差异让我们以此作弊。
&'a T'aT的协变变量。这意味着,只要我们在类型参数中看到 &'a T,就可以用 &'a T的子类型替换它。让我们看看它是如何工作的:
fn main() {
let a = ();
let b = ();
let v = vec![&a, &b];
}

我们已经确定 ab具有不同的生存期,并且表达式 &a&b不具有相同的type1。那么为什么我们可以用这些来制作一个 Vec呢?推理与上述相同,因此我将总结一下: &a强制为 &'b (),因此 v的类型为 Vec<&'b ()>

在方差方面, fn(T)是Rust中的一种特殊情况。 fn(T)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();
}

这样编译。但是 v中的 quux是什么类型?是 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(&());
}

这不会编译。以下是编译器消息:

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 ())>。如您所见,协方差与协方差相反:我们可以用更长的生命周期代替较短的生命周期。

ew,现在回到您的问题。首先,让我们看看编译器从 hint调用中产生了什么。 hint具有以下签名:
fn hint<'a, Arg>(_: &'a Arg) -> Foo<'a>
Foo'a是相反的,因为 Foo包裹了 fn(或者,由于 PhantomData,所以假装为,但是当我们谈论方差时并没有区别;两者具有相同的作用), fn(T)T是相反的,并且 T在这里是 &'a ()

当编译器尝试解决对 hint的调用时,它仅考虑 shortlived的生存期。因此, hint返回具有 Foo生命周期的 shortlived。但是,当我们尝试将其分配给变量 foo时,我们遇到了一个问题:类型上的生存期参数总是超过类型本身,而 shortlived的生存期并不超过 foo的生存期,因此很明显,我们不能使用该类型作为 foo。如果 Foo'a是协变的,那将是它的结尾,并且您会得到一个错误。但是 Foo'a是互变的,因此我们可以将 shortlived的生存期替换为更长的生存期。该生存期可以是任何超过 foo生存期的生存期。请注意,“生存期”与“严格生存期”不同:区别是 'a: 'a( 'a超过 'a)是正确的,但是 'a严格超过 'a是假的(即,生存期被说成是自身的生存期,但它并不存在)完全不存在)。因此,我们可能最终得到类型为 fooFoo<'a>,其中 'a正是 foo本身的生存期。

现在,让我们看一下 check(&foo, &outlived);(这是第二个)。之所以进行编译,是因为 &outlived被强制执行,因此缩短了生存期以匹配 foo的生存期。这是正确的,因为 outlived的生存期比 foo更长,并且 check的第二个参数是 'a的协变变量,因为它是引用。
check(&foo, &shortlived);为什么不编译? foo的生存期比 &shortlived更长。 check的第二个参数与 'a协变,但它的第一个参数与 'a协变,因为 Foo<'a>是协变的。也就是说,这两个参数都试图朝相反的方向拉 'a: &foo试图扩大 &shortlived的生存期(这是非法的),而 &shortlived试图缩短 &foo的生存期(这也是非法的)。没有生存期可以统一这两个变量,因此该调用无效。

1这实际上可能是一种简化。我相信引用的有效期参数实际上代表借用处于 Activity 状态的区域,而不是引用的有效期。在此示例中,两个借用对于包含对 ensure_equal的调用的语句都是有效的,因此它们将具有相同的类型。但是,如果将借用拆分为单独的 let语句,该代码仍然有效,因此说明仍然有效。就是说,要使借阅有效,引用对象必须超过借用区域,因此,当我考虑生命周期参数时,我只关心引用对象的生存期,因此我考虑单独借用。

关于rust - 这个实例看似如何超过其自身的参数生存期?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42637911/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com