为什么这个可变借用超出了它的范围?

Why does this mutable borrow live beyond its scope?

在我预计可变借用结束后,我遇到了一个关于同时使用可变借用和不可变借用的令人困惑的错误。我对类似的问题做了很多研究(, , , , ) which has led me to believe my problem has something to do with lexical lifetimes(虽然打开 NLL 功能并在夜间编译不会改变结果),我只是不知道是什么;我的情况似乎并没有适合其他问题的任何场景。

pub enum Chain<'a> {
    Root {
        value: String,
    },
    Child {
        parent: &'a mut Chain<'a>,
    },
}

impl Chain<'_> {
    pub fn get(&self) -> &String {
        match self {
            Chain::Root { ref value } => value,
            Chain::Child { ref parent } => parent.get(),
        }
    }

    pub fn get_mut(&mut self) -> &mut String {
        match self {
            Chain::Root { ref mut value } => value,
            Chain::Child { ref mut parent } => parent.get_mut(),
        }
    }
}

#[test]
fn test() {
    let mut root = Chain::Root { value: "foo".to_string() };

    {
        let mut child = Chain::Child { parent: &mut root };

        *child.get_mut() = "bar".to_string();
    } // I expect child's borrow to go out of scope here

    assert_eq!("bar".to_string(), *root.get());
}

playground

错误是:

error[E0502]: cannot borrow `root` as immutable because it is also borrowed as mutable
  --> example.rs:36:36
   |
31 |         let mut child = Chain::Child { parent: &mut root };
   |                                                --------- mutable borrow occurs here
...
36 |     assert_eq!("bar".to_string(), *root.get());
   |                                    ^^^^
   |                                    |
   |                                    immutable borrow occurs here
   |                                    mutable borrow later used here

我明白为什么那里会发生不可变借用,但我不明白那里是如何使用可变借用的。两者如何在同一个地方使用?我希望有人能解释发生了什么以及如何避免它。

简而言之,&'a mut Chain<'a>是极其有限和普遍的。

对于不可变引用 &T<'a>,允许编译器在必要时缩短 'a 的生命周期以匹配其他生命周期或作为 NLL 的一部分(这并非 总是 的情况,这取决于 T 是什么)。但是,它不能为可变引用这样做&mut T<'a>,否则你可以为它分配一个更短生命周期的值。

因此,当编译器尝试在链接引用和参数时协调生命周期 &'a mut T<'a>,引用的生命周期在概念上被扩展以匹配参数的生命周期。这实质上意味着您已经创建了一个永远不会被释放的可变借用。

将这些知识应用于您的问题:只有嵌套值在其生命周期内是协变的,才能创建基于引用的层次结构。其中不包括:

  • 可变引用
  • 特征对象
  • 具有内部可变性的结构

请参阅 playground 上的这些变体,了解它们如何不按预期工作。

另请参阅:

  • What are the differences when getting an immutable reference from a mutable reference with self-linked lifetimes?

问题不是词法生命周期,添加显式 drop 不会改变错误。问题在于 &'a mut Chain<'a>- 强制 root 在其整个生命周期内被借用,在借用被删除后使其变得无用。根据下面的评论,在生命周期内这样做基本上是不可能的。我建议改用盒子。将结构更改为

pub enum Chain{
Root {
        value: String,
    },
    Child {
        parent: Box<Chain>,
    },
}

并根据需要调整其他方法。或者,如果您希望原件在不消耗 self 的情况下保持可用,请使用 Rc<RefCell<Chain>>