29 votes

Pourquoi Rust NLL ne fonctionne pas pour plusieurs emprunts dans la même instruction ?

Tout d'abord, j'ai essayé quelque chose comme ceci :

let mut vec = vec![0];
vec.rotate_right(vec.len());

Il ne peut pas être compilé parce que :

error[E0502]: cannot borrow `vec` as immutable because it is also borrowed as mutable

J'ai pensé que le vérificateur de prêt Rust pourrait être plus intelligent que cela, alors j'ai trouvé quelque chose appelé NLL, et cela devrait résoudre ce problème.

J'ai essayé l'exemple :

let mut vec = vec![0];
vec.resize(vec.len(), 0);

Cela pourrait fonctionner, mais pourquoi ça ne fonctionne pas avec rotate_right ? Tous les deux prennent un &mut self. Qu'est-ce qui se passe ?

27voto

Chayim Friedman Points 946

C'est définitivement intéressant.

ils sont similaires - mais pas tout à fait les mêmes. resize() est un membre de Vec. rotate_right(), en revanche, est une méthode des tranches.

Vec se déréfère en [T], donc la plupart du temps cela n'a pas d'importance. Mais en réalité, alors que cet appel :

vec.resize(vec.len(), 0);

Se désucre en quelque chose comme :

>::resize(&mut vec, >::len(&vec), 0);

Cet appel :

vec.rotate_right(vec.len());

Est plus comme :

<[i32]>::rotate_right(
     as DerefMut>::deref_mut(&mut vec),
    >::len(&vec),
);

Mais dans quel ordre ?

Ceci est le MIR pour rotate_right() (simplifié beaucoup) :

fn foo() -> () {
    _4 =  as DerefMut>::deref_mut(move _5);
    _6 = Vec::::len(move _7);
    _2 = core::slice::::rotate_right(move _3, move _6);
}

Et ceci est le MIR pour resize() (encore une fois, simplifié beaucoup) :

fn foo() -> () {
    _4 = Vec::::len(move _5);
    _2 = Vec::::resize(move _3, move _4, const 0_i32);
}

Dans l'exemple de resize(), nous appelons d'abord Vec::len() avec une référence à vec. Cela renvoie usize. Ensuite, nous appelons Vec::resize(), lorsque nous n'avons pas d'autres références en suspens à vec, donc l'emprunt mutable est autorisé !

Cependant, avec rotate_right(), d'abord nous appelons as DerefMut>::deref_mut(&mut vec). Cela renvoie &mut [i32], avec sa durée de vie liée à vec. C'est-à-dire, tant que cette référence (référence mutable !) est active, nous ne sommes pas autorisés à utiliser d'autres références à vec. Mais ensuite nous essayons d'emprunter vec afin de passer la référence (partagée, mais cela n'a pas d'importance) à Vec::len(), alors que nous devons encore utiliser la référence mutable de deref_mut() plus tard, dans l'appel à <[i32]>::rotate_right() ! C'est une erreur.

Cela est dû au fait que Rust définit un ordre d'évaluation pour les opérandes :

Les expressions prenant plusieurs opérandes sont évaluées de gauche à droite telles qu'elles sont écrites dans le code source.

Parce que vec.resize() est en fait (&mut *vec).rotate_right(), nous évaluons d'abord la déréférence + la référence, puis les arguments :

let dereferenced_vec = &mut *vec;
let len = vec.len();
déréférencec_vec.rotate_right(len);

Ce qui est clairement une violation des règles de l'emprunt.

En revanche, vec.resize(vec.len()) n'a aucun travail à faire sur le calle (vec), et donc nous évaluons d'abord vec.len(), puis l'appel lui-même.

Résoudre cela est aussi simple que d'extraire le vec.len() sur une nouvelle ligne (nouvelle instruction, pour être précis), et le compilateur le suggère également.

8voto

kmdreko Points 3321

La seule différence structurelle entre ces deux appels est la cible : rotate_right() est défini sur une tranche, tandis que resize() est défini sur un Vec. Cela signifie que le cas qui ne fonctionne pas comporte une coercition Deref supplémentaire (dans ce cas, DerefMut) qui doit passer le vérificateur d'emprunts.

Cela se situe en réalité en dehors du domaine des règles de durée de vie non-lexicales, car la durée de vie des références n'est pas importante. Cette particularité relève des règles de l'ordre d'évaluation; plus précisément, étant donné vec.rotate_right(vec.len()), quand se produit la coercition de &mut Vec<_> en &mut [_]? Avant ou après vec.len()?

L'ordre d'évaluation pour les appels de fonctions et de méthodes est de gauche à droite, donc la coercition doit être évaluée avant les autres paramètres, ce qui signifie que vec est déjà mutuellement emprunté avant que vec.len() ne soit appelé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X