简体   繁体   English

这个实例如何看似比自己的参数生命周期更长?

[英]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 ,我们定义了两个变量ab 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派生自PersonProgrammer因此是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 a T and returns a Vec<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'aT '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 . 我们已经确定ab具有不同的生命周期,并且表达式&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 ? 但是quuxv的类型是什么? 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)是逆变在TT这里是&'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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM