Was bedeutet "kann nicht als unveränderlich ausgeliehen werden, weil es auch als veränderlich ausgeliehen ist" in einem verschachtelten Array-Index?

16

Was bedeutet der Fehler in diesem Fall:

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

Ich fand , dass die Indizierung über die implementiert ist Indexund IndexMutZüge und v[1]ist syntaktischer Zucker für *v.index(1). Ausgestattet mit diesem Wissen habe ich versucht, den folgenden Code auszuführen:

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;
}

Zu meiner Überraschung funktioniert das einwandfrei! Warum funktioniert das erste Snippet nicht, das zweite jedoch? So wie ich die Dokumentation verstehe, sollten sie gleichwertig sein, aber dies ist offensichtlich nicht der Fall.

Lucas Boucke
quelle
2
Rost mit dem Aufkommen von Code lernen? Willkommen bei StackOverflow und vielen Dank für die tolle Frage!
Sven Marnach
Genau; ) Dies ist mein drittes Jahr (2x Haskell davor) ~> dachte, ich würde Rust einen Wirbel geben, da ich mich mehr für Sachen auf niedrigem Niveau interessiere
Lucas Boucke
@LucasBoucke Das ist lustig, ich benutze normalerweise Rust für mein Projekt, aber ich schreibe diese AoC in Haskell. Sie sind beide großartige Sprachen in ihrer Domäne.
Boiethios

Antworten:

16

Die Desugared-Version unterscheidet sich geringfügig von Ihrer Version. Die Linie

v[v[1]] = 999;

eigentlich Desugars zu

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

Dies führt zu derselben Fehlermeldung, aber die Anmerkungen geben einen Hinweis darauf, was passiert:

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

Der wichtige Unterschied zu Ihrer Desugared-Version ist die Bewertungsreihenfolge. Die Argumente eines Funktionsaufrufs werden von links nach rechts in der angegebenen Reihenfolge ausgewertet, bevor der Funktionsaufruf tatsächlich ausgeführt wird. In diesem Fall bedeutet dies, dass zunächst &mut veine stimmbare Ausleihe ausgewertet wird v. Als nächstes Index::index(&v, 1)sollte ausgewertet werden, aber dies ist nicht möglich - vist bereits veränderlich ausgeliehen. Schließlich zeigt der Compiler, dass die veränderbare Referenz für den Funktionsaufruf noch benötigt wird index_mut(), sodass die veränderbare Referenz noch aktiv ist, wenn versucht wird, die gemeinsam genutzte Referenz aufzurufen .

Die tatsächlich kompilierte Version hat eine etwas andere Auswertungsreihenfolge.

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

Zunächst werden die Funktionsargumente für die Methodenaufrufe von links nach rechts *v.index(1)ausgewertet , dh zuerst ausgewertet. Dies führt zu einem usize, und das temporäre gemeinsame Ausleihen von vkann wieder freigegeben werden. Dann wird der Empfänger von index_mut()ausgewertet, dh vveränderlich ausgeliehen. Dies funktioniert einwandfrei, da der gemeinsame Kredit bereits abgeschlossen wurde und der gesamte Ausdruck den Kreditprüfer besteht.

Beachten Sie, dass die kompilierte Version dies erst seit Einführung der "nicht-lexikalischen Lebensdauer" tut. In früheren Versionen von Rust blieb die gemeinsame Ausleihe bis zum Ende des Ausdrucks bestehen und führte zu einem ähnlichen Fehler.

Die meiner Meinung nach sauberste Lösung ist die Verwendung einer temporären Variablen:

let i = v[1];
v[i] = 999;
Sven Marnach
quelle
Woah! Hier ist viel los! Vielen Dank, dass Sie sich die Zeit genommen haben, dies zu erklären! (Interessanterweise machen solche "Macken" eine Sprache für mich interessanter ...). Könnten Sie vielleicht auch einen Hinweis geben, warum ein *v.index_mut(*v.index_mut(1)) = 999;Fehler mit "kann v nicht mehr als einmal als veränderbar ausleihen" fehlschlägt ~> sollte der Compiler nicht sein, da *v.index_mut(*v.index(1)) = 999;er herausfinden kann, dass das innere Ausleihen nicht mehr benötigt wird?
Lucas Boucke
@LucasBoucke Rust hat einige Macken, die manchmal etwas unpraktisch sind, aber in den meisten Fällen ist die Lösung ziemlich einfach, wie in diesem Fall. Der Code ist immer noch gut lesbar, nur ein kleines bisschen anders als ursprünglich, also ist es in der Praxis keine große Sache.
Sven Marnach
@LucasBoucke Sorry, ich habe deine Bearbeitung bis jetzt nicht gesehen. Das Ergebnis von *v.index(1)ist der Wert , der in diesem Index gespeichert ist, und dieser Wert erfordert nicht, dass die Ausleihe am vLeben bleibt . Das Ergebnis von *v.index_mut(1)ist andererseits ein veränderlicher Ortsausdruck , der theoretisch zugeordnet werden könnte, so dass der Kredit am Leben bleibt. Oberflächlich betrachtet sollte es möglich sein, dem Ausleihprüfer beizubringen, dass ein Ortsausdruck im Wertausdruckskontext als Wertausdruck behandelt werden kann, sodass dies möglicherweise in einer zukünftigen Version von Rust kompiliert wird.
Sven Marnach
Wie wäre es mit einem RFC, um dies zu beheben:{ let index = *Index::index(&v, 1); let value = 999; *IndexMut::index_mut(&mut v, index) = value; }
Boiethios
@FrenchBoiethios Ich habe keine Ahnung, wie Sie das formalisieren würden, und ich bin sicher, es wird nie fliegen. Wenn Sie dies ansprechen möchten, sehe ich nur Verbesserungen am Ausleihprüfer, z. B. durch Erkennen, dass der veränderbare Ausleihen zu einem späteren Zeitpunkt beginnen kann, da er nicht so früh wirklich benötigt wird. (Diese spezielle Idee funktioniert wahrscheinlich auch nicht.)
Sven Marnach