ネストされた配列インデックスで「変更可能としても借りられるため、不変として借りることができない」とはどういう意味ですか?


16

この場合のエラーの意味:

fn main() {
    let mut v: Vec<usize> = vec![1, 2, 3, 4, 5];
    v[v[1]] = 999;
}
error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:3:7
  |
3 |     v[v[1]] = 999;
  |     --^----
  |     | |
  |     | immutable borrow occurs here
  |     mutable borrow occurs here
  |     mutable borrow later used here

私は、インデックスを経由して実装されていることが判明Indexし、IndexMut特性とそれがv[1]ためのシンタックスシュガーです*v.index(1)。この知識を備えて、私は次のコードを実行しようとしました:

use std::ops::{Index, IndexMut};

fn main() {
    let mut v: Vec<usize> = vec![1, 2, 3, 4, 5];
    *v.index_mut(*v.index(1)) = 999;
}

驚いたことに、これは完璧に機能します!最初のスニペットが機能しないのに、2番目のスニペットは機能するのはなぜですか?私がドキュメントを理解する方法では、それらは同等であるはずですが、これは明らかにそうではありません。


2
コードの出現でRustを学ぶ?StackOverflowへようこそ。すばらしい質問をありがとうございます。
Sven Marnach

正確に; )今年で3年目(その前にHaskellが2回)〜>低レベルのものに興味を持ち始めたので、Rustに旋風を与えると思った
Lucas Boucke

@LucasBoucke面白いことに、私は通常、Rustをプロジェクトに使用していますが、このAoCをHaskellで作成しています。どちらもドメインの優れた言語です。
ボイエチオス

回答:


16

脱糖バージョンは、お持ちのバージョンとは少し異なります。この線

v[v[1]] = 999;

実際にdesugars

*IndexMut::index_mut(&mut v, *Index::index(&v, 1)) = 999;

これにより同じエラーメッセージが表示されますが、注釈は何が起こっているかについてのヒントを提供します。

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:48
  |
7 |     *IndexMut::index_mut(&mut v, *Index::index(&v, 1)) = 999;
  |      ------------------- ------                ^^ immutable borrow occurs here
  |      |                   |
  |      |                   mutable borrow occurs here
  |      mutable borrow later used by call

脱糖バージョンとの重要な違いは、評価順序です。関数呼び出しの引数は、実際に関数呼び出しを行う前に、リストされている順序で左から右に評価されます。この場合、これは最初&mut vに評価され、可変的に借用されることを意味しvます。次に、Index::index(&v, 1)評価する必要がありますが、これは不可能です– vすでに可変的に借用されています。最後に、コンパイラは、への関数呼び出しに変更可能な参照がまだ必要であることを示しているindex_mut()ため、共有参照が試行されても、変更可能な参照はまだ有効です。

実際にコンパイルするバージョンでは、評価順序が少し異なります。

*v.index_mut(*v.index(1)) = 999;

最初に、メソッド呼び出しの関数引数が左から右に*v.index(1)評価されます。つまり、最初に評価されます。これによりが発生しusize、の一時的な共有ボローをv再び解放できます。次に、の受信者index_mut()が評価されvます。つまり、可変的に借用されます。共有された借用はすでに確定されており、式全体が借用チェッカーを通過するため、これは正常に機能します。

「非語彙的ライフタイム」の導入以来、コンパイルするバージョンはそうするだけであることに注意してください。Rustの以前のバージョンでは、共有された借用は式の終わりまで存続し、同様のエラーが発生しました。

私の意見で最もクリーンな解決策は、一時変数を使用することです:

let i = v[1];
v[i] = 999;

わあ!ここではたくさんのことが起こっています!説明してくれてありがとう!(興味深いことに、これらの種類の「癖」は、言語を私にとってより興味深いものにします...)。また*v.index_mut(*v.index_mut(1)) = 999;、「vを可変として借りることができない」で失敗する理由についてのヒントも教えてください*v.index_mut(*v.index(1)) = 999;
Lucas Boucke

@LucasBoucke Rustには、時々少し不便ないくつかの癖がありますが、ほとんどの場合、この場合のように、ソリューションはかなり単純です。コードはまだかなり読みやすく、元々持っていたものはほんの少し違うだけなので、実際には大したことではありません。
Sven Marnach

@LucasBoucke申し訳ありませんが、あなたの編集は今まで見ていません。の結果はそのインデックスに保存され*v.index(1)であり、その値は借用を存続させる必要はありませんv*v.index_mut(1)一方、の結果は、理論的に割り当て可能な変更可能な場所の式であるため、借用を存続させます。表面上、借用チェッカーに値式コンテキスト内の場所式を値式として処理できることを教えることができるはずです。そのため、これはRustの将来のバージョンでコンパイルされる可能性があります。
Sven Marnach

これをdesugarするRFCはどうですか:{ let index = *Index::index(&v, 1); let value = 999; *IndexMut::index_mut(&mut v, index) = value; }
Boiethios

@FrenchBoiethios私はあなたがそれをどのように形式化するかわからない、そしてそれが飛ぶのにうんざりすることは決してないと確信している。これに対処したい場合、私が見る唯一の方法は借用チェッカーの改善です。たとえば、変更可能な借用が後から開始できることを検出することです。(この特定のアイデアもおそらく機能しないでしょう。)
Sven Marnach
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.