この可変借用はなぜその範囲を超えて存続するのでしょうか?

概要

可変借用が終了すると予想した後、可変借用と不変借用を同時に使用すると、混乱を招くエラーが発生しました。私は同様の質問(1、2、3、4、5)について多くの調査を行った結果、私の問題は字句の有効期間に関係があると考えるようになりました(ただし、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());
}

遊び場

エラーは次のとおりです。

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> の場合、コンパイラは、他のライフタイムと一致させるために必要な場合、または NLL の一部として、’a のライフタイムを短縮することができます (これは常に当てはまるわけではなく、T が何であるかによって異なります)。ただし、可変参照 &mut T<’a> の場合はそうすることができません。それ以外の場合は、より短い有効期間の値を割り当てることができます。

したがって、参照とパラメーターが &’a mut T<’a> にリンクされているときにコンパイラーが有効期間を調整しようとすると、参照の有効期間は概念的にパラメーターの有効期間と一致するように拡張されます。これは本質的に、決して解放されない変更可能な借用を作成したことを意味します。

その知識を質問に適用すると、参照ベースの階層を作成できるのは、入れ子になった値が存続期間にわたって共変する場合にのみ可能です。以下を除きます。

プレイグラウンドのこれらのバリエーションを参照して、これらがどのように期待どおりに機能しないかを確認してください。

以下も参照してください。

他のコンパイラ エラーにより、これらのライフタイムを一致させる必要があると思われる場合は、「自己参照構造体」を作成している可能性があります。それが実際に機能しない理由をここで参照してください。

楽しみのために、Rust 標準ライブラリがこの種のことを意図的に行うケースを含めます。 std::thread::scope の署名は次のようになります。

pub fn scope<'env, F, T>(f: F) -> T
where
    F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T

ユーザー定義関数に提供されるスコープは、意図された方法でのみ使用されるように、その有効期間が意図的に結び付けられています。構造体はジェネリック型に対して共変または反変である可能性があるため、これは常に当てはまるわけではありませんが、スコープは不変であるように定義されています。また、呼び出すことができる唯一の関数は .spawn() です。これは意図的に &’scope self を自己パラメータとしても取り、参照の有効期間がスコープで指定されたものよりも短くならないようにします。

内部的には、標準ライブラリには次のドキュメント (ソース) が含まれています。

参照の存続期間がそれ自体に関して不変であっても、不変参照と内部可変性を使用するため、上記の多くの問題は回避されます。 .spawn() のパラメータに &’scope mut self が必要な場合、これは機能せず、複数のスレッドを生成しようとすると上記と同じ問題が発生します。