Que signifie «ne peut pas emprunter en tant qu'immuable parce qu'il est également emprunté en tant que mutable» signifie dans un index de tableau imbriqué?

16

Que signifie l'erreur dans ce cas:

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

Je trouve que l' indexation est mis en œuvre par les Indexet IndexMuttraits et v[1]est du sucre syntaxique pour *v.index(1). Muni de ces connaissances, j'ai essayé d'exécuter le code suivant:

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

À ma grande surprise, cela fonctionne parfaitement! Pourquoi le premier extrait ne fonctionne-t-il pas, alors que le second fonctionne? D'après ma compréhension de la documentation, ils devraient être équivalents, mais ce n'est évidemment pas le cas.

Lucas Boucke
la source
2
Apprendre la rouille avec l'avènement du code? Bienvenue sur StackOverflow, et merci pour la grande question!
Sven Marnach
Précisément ; ) C'est ma 3ème année de travail (2x Haskell avant ça) ~> j'ai pensé donner un tourbillon à Rust depuis que j'ai commencé à être plus intéressé par les trucs de bas niveau
Lucas Boucke
@LucasBoucke C'est drôle, j'utilise habituellement Rust pour mon projet, mais j'écris cet AoC en Haskell. Ils sont tous les deux d'excellentes langues dans leur domaine.
Boiethios

Réponses:

16

La version désucrée est légèrement différente de ce que vous avez. La ligne

v[v[1]] = 999;

fait desugars à

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

Il en résulte le même message d'erreur, mais les annotations donnent une indication sur ce qui se passe:

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

La différence importante par rapport à votre version désucrée est l'ordre d'évaluation. Les arguments d'un appel de fonction sont évalués de gauche à droite dans l'ordre indiqué, avant d'effectuer réellement l'appel de fonction. Dans ce cas, cela signifie que le premier &mut vest évalué, en empruntant mutuellement v. Ensuite, Index::index(&v, 1)devrait être évalué, mais ce n'est pas possible - vest déjà mutuellement emprunté. Enfin, le compilateur montre que la référence mutable est toujours nécessaire pour l'appel de fonction à index_mut(), de sorte que la référence mutable est toujours active lorsque la référence partagée est tentée.

La version qui compile a un ordre d'évaluation légèrement différent.

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

Tout d'abord, les arguments de fonction des appels de méthode sont évalués de gauche à droite, c'est *v.index(1)-à- dire qu'ils sont évalués en premier. Il en résulte un usizeet l'emprunt partagé temporaire de vpeut être à nouveau libéré. Ensuite, le récepteur de index_mut()est évalué, c'est v-à- dire qu'il est mutuellement emprunté. Cela fonctionne très bien, car l'emprunt partagé a déjà été finalisé et l'expression entière passe le vérificateur d'emprunt.

Notez que la version qui compile ne le fait que depuis l'introduction des "durées de vie non lexicales". Dans les versions antérieures de Rust, l'emprunt partagé durerait jusqu'à la fin de l'expression et entraînerait une erreur similaire.

La solution la plus propre à mon avis est d'utiliser une variable temporaire:

let i = v[1];
v[i] = 999;
Sven Marnach
la source
Woah! Il se passe beaucoup de choses ici! Merci d'avoir pris le temps de m'expliquer! (fait intéressant, ce genre de "bizarreries" rend un langage plus intéressant pour moi ...). Pourriez-vous peut-être également nous expliquer pourquoi *v.index_mut(*v.index_mut(1)) = 999;échoue avec "ne peut pas emprunter v en tant que mutable plus d'une fois" ~> le compilateur ne devrait-il pas l'être, comme avec la *v.index_mut(*v.index(1)) = 999;possibilité de comprendre que l'emprunt interne n'est plus nécessaire?
Lucas Boucke
@LucasBoucke Rust a quelques bizarreries qui sont parfois un peu gênantes, mais dans la plupart des cas, la solution est plutôt simple, comme dans ce cas. Le code est toujours assez lisible, juste un tout petit peu différent de ce que vous aviez à l'origine, donc en pratique ce n'est pas un gros problème.
Sven Marnach
@LucasBoucke Désolé, je n'ai pas vu votre modification jusqu'à présent. Le résultat de *v.index(1)est la valeur stockée à cet index, et cette valeur ne nécessite pas de conserver l'emprunt de vvivant. Le résultat de *v.index_mut(1), d'autre part, est une expression de lieu mutable qui pourrait théoriquement être assignée à, donc elle maintient l'emprunt en vie. En surface, il devrait être possible d'enseigner au vérificateur d'emprunt qu'une expression de lieu dans un contexte d'expression de valeur peut être traitée comme une expression de valeur, il est donc possible que cela soit compilé dans une future version de Rust.
Sven Marnach
Que diriez-vous d'un RFC pour désugar ceci à:{ let index = *Index::index(&v, 1); let value = 999; *IndexMut::index_mut(&mut v, index) = value; }
Boiethios
@FrenchBoiethios Je n'ai aucune idée de la façon dont vous formaliseriez cela, et je suis sûr que ça ne va jamais de voler. Si vous voulez résoudre ce problème, la seule façon que je vois est en améliorant le vérificateur d'emprunt, par exemple en lui faisant détecter que l'emprunt mutable peut commencer plus tard, car il n'est pas vraiment nécessaire si tôt. (Cette idée particulière ne fonctionne probablement pas non plus.)
Sven Marnach