gpt4 book ai didi

rust - 即使存在 `&mut T` ,我是否可以将生命周期参数强制为较短的生命周期(健全)?

转载 作者:行者123 更新时间:2023-12-01 10:17:15 27 4
gpt4 key购买 nike

我正在尝试用 Rust 中的父指针制作一棵树。节点结构上的一种方法给我带来了终身问题。这是一个最小的例子,明确地写了生命周期,以便我可以理解它们:

use core::mem::transmute;

pub struct LogNode<'n>(Option<&'n mut LogNode<'n>>);

impl<'n> LogNode<'n> {
pub fn child<'a>(self: &'a mut LogNode<'n>) -> LogNode<'a> {
LogNode(Some(self))
}

pub fn transmuted_child<'a>(self: &'a mut LogNode<'n>) -> LogNode<'a> {
unsafe {
LogNode(Some(
transmute::<&'a mut LogNode<'n>, &'a mut LogNode<'a>>(self)
))
}
}
}

( Playground link )

Rust 提示 child ...

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter 'n due to conflicting requirements



...但它很好 transmuted_child .

我想我明白为什么 child不会编译: self参数的类型是 &'a mut LogNode<'n>但是子节点包含一个 &'a mut LogNode<'a> , 而 Rust 不想强制 LogNode<'n>LogNode<'a> .如果我将可变引用更改为共享引用, it compiles fine ,所以听起来可变引用是一个问题,特别是因为 &mut TT 上是不变的(而 &T 是协变的)。我猜 LogNode 中的可变引用泡起来做 LogNode本身在其生命周期参数内不变。

但我不明白为什么这是真的 - 直觉上感觉采取 LogNode<'n> 是完全合理的。并通过将其转换为 LogNode<'a> 来缩短其内容的生命周期.由于没有生命周期更长,因此无法在生命周期之后访问任何值,而且我想不出可能发生的任何其他不良行为。
transmuted_child避免了生命周期问题,因为它避开了借用检查器,但我不知道使用不安全的 Rust 是否合理,即使是这样,如果可能的话,我更愿意使用安全的 Rust。我可以吗?

对于这个问题,我可以想到三个可能的答案:
  • child可以完全在安全的 Rust 中实现,这里是如何实现的。
  • child不能完全在安全的 Rust 中实现,但是 transmuted_child是声音。
  • child不能完全在安全的 Rust 中实现,并且 transmuted_child不健全。

  • 编辑 1:修复了 &mut T 的声明在引用的生命周期内是不变的。 (没有正确阅读 nomicon。)

    编辑 2:修复了我的第一个编辑摘要。

    最佳答案

    要了解为什么不可变版本有效而​​可变版本不健全(如所写),我们必须讨论 subtyping and variance .

    Rust 大多没有子类型。值通常具有唯一类型。然而,Rust 确实有子类型的一个地方是生命周期。如 'a: 'b (读 'a'b 长),然后,例如, &'a T&'b T 的子类型,直觉上是因为更长的生命周期可以被视为更短的生命周期。

    方差是子类型传播的方式。如 AB 的子类型,我们有一个泛型类型 Foo<T> , Foo<A>可能是 Foo<B> 的子类型,反之亦然,或两者都不是。在第一种情况下,子类型的方向保持不变,Foo<T>据说是关于 T 的协变的.在第二种情况下,方向相反,它被称为逆变,在第三种情况下,它被称为不变。

    对于这种情况,相关类型是 &'a T&'a mut T .两者在 'a 中都是协变的(因此可以将具有较长生命周期的引用强制转换为具有较短生命周期的引用)。 &'a TT 中是协变的,但是 &'a mut TT 中是不变的.

    其原因在 Nomicon(上面链接)中进行了解释,因此我将仅向您展示那里给出的(稍微简化的)示例。 Trentcl 的代码是如果 &'a mut T 出错的工作示例在 T 中是协变的.

    fn evil_feeder(pet: &mut Animal) {
    let spike: Dog = ...;

    // `pet` is an Animal, and Dog is a subtype of Animal,
    // so this should be fine, right..?
    *pet = spike;
    }

    fn main() {
    let mut mr_snuggles: Cat = ...;
    evil_feeder(&mut mr_snuggles); // Replaces mr_snuggles with a Dog
    mr_snuggles.meow(); // OH NO, MEOWING DOG!
    }

    那么为什么 child 的不可变版本工作,但不是可变版本?在不可变版本中, LogNode包含对 LogNode 的不可变引用,因此通过生命周期和类型参数的协方差, LogNode其生命周期参数是协变的。如 'a: 'b ,然后 LogNode<'a>LogNode<'b> 的子类型.

    我们有 self: &'a LogNode<'n> ,这意味着 'n: 'a (否则这个借用将比 LogNode<'n> 中的数据更持久)。因此,由于 LogNode是协变的, LogNode<'n>LogNode<'a> 的子类型.此外,不可变引用中的协方差再次允许 &'a LogNode<'n>成为 &'a LogNode<'a> 的子类型.因此, self: &'a LogNode<'n>可以强制到 &'a LogNode<'a>根据 child 中的返回类型的需要.

    对于可变版本, LogNode<'n>'n 中不是协变的.这里的方差归结为 &'n mut LogNode<'n>的方差。 .但是由于这里的可变引用的“ T ”部分有一个生命周期,可变引用的不变性(在 T 中)意味着这也必须是不变的。

    这一切结合起来表明 self: &'a mut LogNode<'n>不能被强制到 &'a mut LogNode<'a> .所以函数不能编译。

    对此的一种解决方案是添加生命周期限制 'a: 'n ,尽管如上所述,我们已经有了 'n: 'a ,所以这迫使两个生命周期相等。这可能适用于您的其余代码,也可能不起作用,因此请谨慎对待。

    关于rust - 即使存在 `&mut T` ,我是否可以将生命周期参数强制为较短的生命周期(健全)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60254478/

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