【问题标题】:Why does this mutable borrow live beyond its scope?为什么这个可变借用超出了它的范围?
【发布时间】:2021-05-20 23:37:26
【问题描述】:

在我期望可变借用结束之后,我遇到了一个关于同时使用可变借用和不可变借用的令人困惑的错误。我对类似问题(12345)进行了大量研究,这让我相信我的问题与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

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

【问题讨论】:

    标签: rust borrow-checker


    【解决方案1】:

    简而言之,&amp;'a mut Chain&lt;'a&gt; 具有极大的局限性和普遍性。

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

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

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

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

    请参阅playground 上的这些变体,了解这些变体与预期的工作方式有何不同。

    另见:

    【讨论】:

    • 谢谢。我现在明白为什么它不起作用了。我认为我寻求的解决方案是您提供的链接之一,尤其是责任链之一——这几乎正是我正在尝试实施的模式。
    【解决方案2】:

    问题不在于词法生命周期,添加显式 drop 不会改变错误。问题在于&amp;'a mut Chain&lt;'a&gt;- 强制root 在其整个生命周期中被借用,在借用被丢弃后使其无用。根据下面的评论,用生命周期来做到这一点基本上是不可能的。我建议改用盒子。将结构更改为

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

    并根据需要调整其他方法。或者,如果您希望原件保持可用而不消耗自身,请使用Rc&lt;RefCell&lt;Chain&gt;&gt;

    【讨论】:

    • @kmdreko 你说得对,我不是很聪明。固定。
    • 谢谢。我也许可以重构我的设计以使用Box,但我经常在链的末端推送和弹出这些子对象,因此很难考虑它是否不属于根。
    • @rmeador 可能值得一试Rc&lt;RefCell&lt;Chain&gt;&gt;Rc 允许您共享所有权,RefCell 允许您动态而不是静态地强制执行 RAII。如果您这样做,请注意参考周期。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-10
    • 2020-07-04
    • 1970-01-01
    • 2018-03-06
    • 1970-01-01
    • 1970-01-01
    • 2022-11-25
    相关资源
    最近更新 更多