明确的终身注释玩具示例

发布于 2025-01-26 18:44:01 字数 393 浏览 3 评论 0原文

我正在尝试找出生锈的终身问题,并在将其煮沸后,我意识到我不知道如何明确注释rx2 和_arr in foo

struct X<'a> {
    _v: &'a mut i32,
}

fn main() {
    let mut v1 = 0;
    let x1 = X { _v: &mut v1 };
    foo(x1);
}

fn foo(x1: X) {
    let mut v2 = 1;
    let r = &mut v2;
    let x2 = X { _v: r };
    let _arr = [x1, x2];
}

I'm trying to figure out a Rust lifetime issue and after boiling it down a bunch, I realized that I have no idea how I would explicitly annotate the lifetimes of r, x2, and _arr in foo:

struct X<'a> {
    _v: &'a mut i32,
}

fn main() {
    let mut v1 = 0;
    let x1 = X { _v: &mut v1 };
    foo(x1);
}

fn foo(x1: X) {
    let mut v2 = 1;
    let r = &mut v2;
    let x2 = X { _v: r };
    let _arr = [x1, x2];
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

辞别 2025-02-02 18:44:01

我将摆脱一个假设,即您的代码部分被切断的部分是:

struct X<'a> {
    _v: &'a mut i32,
}

在这种情况下,让我们稍微解剖foo中的情况。

fn foo(x1: X) {
    // Lives until the end of the function, call its lifetime 'v2
    let mut v2 = 1;
    // Borrow v2 for some lifetime 'r which is no longer than 'v2
    // so r is of type &'r mut i32
    let r = &mut v2;
    let x2 = X { _v: r }; // x2 is of type X<'r>
    let _arr = [x1, x2]; // ??
}

混乱可能来自以下事实:x1似乎具有模棱两可的寿命。这是因为我们没有在x1:x中明确指定寿命。这是一个生锈的2018成语,我个人建议不要这样做。您可以将#![deny(rust_2018_idioms)]添加到板条箱根的顶部,编译器将向您指出这些歧义,并迫使您更加明确。这里发生的事情是函数声明被删除到以下内容:

fn foo<'x1>(x1: X<'x1>) { ... }

现在暗示Lifetime 'x1至少通过foo的主体扩展,这就是有意义,因为它必须这样做。如果在'x1的生​​活中被释放在foo的中间安全。

话虽如此,让我们重新审视这一行:

let _arr = [x1, x2];

我们知道x2是类型x&lt;'r&gt;,并且该'r在大多数到foo的结尾(因为'r不超过'v2'v2扩展到foo的结尾)。此外,我们知道x1是类型x&lt;'x1&gt;,并且该'x1至少扩展到的末尾foo。这意味着'x1至少与'r一样长。由于寿命是协变量的,因此这意味着x&lt;'x1&gt;x&lt;'r&gt;的子类型,因为只要我们需要类型x&lt;'r&gt; 我们可以将其安全地替换为x&lt;'x1&gt;的一种。因此,发生的是_arr被赋予类型[X&lt;'r&gt;;; 2],并且在施工后,x1上的寿命缩短为'r

我们实际上可以检验此假设,以查看是否正确。如果不允许编译器缩短一生,将会发生什么?换句话说,如果x上的寿命不是协变量怎么办。如果我们修改x,则其寿命类型参数是不变的:

struct X<'a> {
    _v: &'a mut i32,
    _invariant: PhantomData<fn(&'a ()) -> &'a ()>
}

果然,在适当的位置添加_invariant字段之后以下错误:

   |
14 | fn foo<'x1>(x1: X<'x1>) {
   |        --- lifetime `'x1` defined here
15 |     let mut v2 = 1;
16 |     let r = &mut v2;
   |             ^^^^^^^ borrowed value does not live long enough
17 |     let x2 = X { _v: r, _invariant: PhantomData };
   |                      - this usage requires that `v2` is borrowed for `'x1`
18 |     let _arr = [x1, x2];
19 | }
   | - `v2` dropped here while still borrowed

现在我们如何知道编译器不将Lifetime 'r'to 'x1首先扩展到?好吧,如果是这样做的,那么我们可以修改foo执行以下操作,这明确会导致免费使用时:

fn foo<'x1>(x1: X<'x1>) -> &'x1 mut i32 {
    let mut v2 = 1;
    let r = &mut v2;
    let x2 = X { _v: r };
    let _arr = [x1, x2];
    _arr[1]._v
}

如果您尝试上述代码,则可以肯定的是,它无法使用原因是我们正在返回对本地变量的引用。此外,如果您尝试返回_arr [0] ._ v,则获得完全相同的错误。

亚型和差异可能很难掌握,这不是您需要充分理解的有效的Rust程序员。尽管如此,它还是非常有趣的,您可以了解更多有关它的信息在这里 Rustonomicon。

I'm going to work off of the assumption that the part of your code that got cut off is the following:

struct X<'a> {
    _v: &'a mut i32,
}

In that case, let's dissect what's going on in foo a little bit.

fn foo(x1: X) {
    // Lives until the end of the function, call its lifetime 'v2
    let mut v2 = 1;
    // Borrow v2 for some lifetime 'r which is no longer than 'v2
    // so r is of type &'r mut i32
    let r = &mut v2;
    let x2 = X { _v: r }; // x2 is of type X<'r>
    let _arr = [x1, x2]; // ??
}

The confusion probably comes from the fact that x1 seems to have an ambiguous lifetime in its type. This is because we didn't explicitly specify the lifetime in x1: X. This is a Rust 2018 idiom, and I personally recommend not doing this. You can add #![deny(rust_2018_idioms)] to the top of your crate root and the compiler will point out these ambiguities to you and force you to be more explicit. What happens here is that the function declaration gets de-sugared to the following:

fn foo<'x1>(x1: X<'x1>) { ... }

Now it is implied that the lifetime 'x1 extends at least through the body of foo, and this makes sense because it kind of has to. If something which lived for 'x1 was freed in the middle of foo (disregarding how something like that could even happen), then that would defeat the point of using lifetimes for memory safety.

So that being said, let's revisit this line:

let _arr = [x1, x2];

We know that x2 is of the type X<'r> and that 'r extends at most to the end of foo (since 'r is no longer than 'v2 and 'v2 extends to the end of foo). Moreover, we know that x1 is of the type X<'x1> and that 'x1 extends at least to the end of foo. This means that 'x1 is at least as long as 'r. Since lifetimes are covariant, this means that X<'x1> is a sub-type of X<'r>, since whenever we require a value of the type X<'r> we can safely substitute in one of type X<'x1> instead. So what happens is _arr is given the type [X<'r>; 2], and upon construction the lifetime on x1 is shortened to 'r.

We can actually test this hypothesis to see if it's correct. What would happen if the compiler wasn't allowed to shorten that lifetime? In other words, what if the lifetime on X was not covariant. If we modify X as follows then its lifetime type parameter is made invariant:

struct X<'a> {
    _v: &'a mut i32,
    _invariant: PhantomData<fn(&'a ()) -> &'a ()>
}

And sure enough, after adding the _invariant field in the appropriate places to make the code compile, we receive the following error:

   |
14 | fn foo<'x1>(x1: X<'x1>) {
   |        --- lifetime `'x1` defined here
15 |     let mut v2 = 1;
16 |     let r = &mut v2;
   |             ^^^^^^^ borrowed value does not live long enough
17 |     let x2 = X { _v: r, _invariant: PhantomData };
   |                      - this usage requires that `v2` is borrowed for `'x1`
18 |     let _arr = [x1, x2];
19 | }
   | - `v2` dropped here while still borrowed

Now how do we know the compiler wasn't extending the lifetime 'r to 'x1 in the first place? Well if it was doing that, then we could modify foo to do the following, which would unequivocally cause a use-after-free:

fn foo<'x1>(x1: X<'x1>) -> &'x1 mut i32 {
    let mut v2 = 1;
    let r = &mut v2;
    let x2 = X { _v: r };
    let _arr = [x1, x2];
    _arr[1]._v
}

And sure enough if you try the code above it fails to compile with the reason given being that we're returning a reference to a local variable. Moreover, if you try returning _arr[0]._v, then you get the exact same error.

Sub-typing and variance can be pretty hard to grasp and it's not something you need to fully understand to be an effective Rust programmer. Nonetheless, it is very interesting and you can learn more about it here in the Rustonomicon.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文