Erreur Rust E0502 : cannot borrow as mutable because it is also borrowed as immutable
E0502 veut dire que vous avez essayé de modifier une valeur alors qu’une autre partie de votre code était encore en train de la lire à travers une référence. Cette erreur apparaît sans arrêt avec les vecteurs, et la correction consiste généralement à déplacer une ligne ou à copier une valeur. Cette page vous donne d’abord la solution, puis explique pourquoi Rust y tient autant. Pour les autres erreurs de cette série, consultez les erreurs du compilateur Rust.
Le message d’erreur
Section intitulée « Le message d’erreur »error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> src/main.rs:4:5 |3 | let first = &v[0]; // a reference into the Vec | - immutable borrow occurs here4 | v.push(4); // ❌ changes the Vec while `first` is alive | ^^^^^^^^^ mutable borrow occurs here5 | println!("First: {first}"); // ...because `first` is used here | ----- immutable borrow later used hereTraduit, le compilateur dit : « impossible d’emprunter v en mutable, car il est aussi emprunté en immuable ». Les trois étiquettes indiquent où l’emprunt immuable commence, où l’emprunt mutable arrive, et où l’emprunt immuable sert encore ensuite.
Voici le programme qui la provoque :
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; // a reference into the Vec v.push(4); // ❌ changes the Vec while `first` is alive println!("First: {first}"); // ...because `first` is used here}(Les commentaires disent : une référence dans le Vec ; on modifie le Vec pendant que first est vivante… parce que first est utilisée ici.)
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Le compilateur pointe trois lignes, et il vous faut les trois pour comprendre l’erreur :
- Ligne 3 :
&v[0]crée une référence en lecture seule versv. C’est l’emprunt immuable. - Ligne 4 :
v.push(4)doit modifierv, donc il prend un emprunt mutable. - Ligne 5 :
firstest utilisée à nouveau. C’est cette ligne qui garde le premier emprunt en vie pendant la ligne 4.
Sans la ligne 5, le code compilerait. Le problème n’est pas que les deux emprunts existent, c’est qu’ils se chevauchent.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »La règle est : plusieurs lecteurs ou un seul rédacteur, jamais les deux en même temps. Vous l’avez vue à la page Le borrowing (l’emprunt).
Pour un Vec, il y a une raison très concrète. Un Vec garde ses éléments dans un seul bloc de mémoire. Quand vous faites push et que ce bloc est plein, le Vec déplace tout vers un nouveau bloc plus grand et libère l’ancien. Toute référence vers l’ancien bloc (comme first) pointe alors vers de la mémoire libérée. En C++, c’est le fameux bug d’« invalidation d’itérateur », qui peut faire planter votre programme ou lui faire lire en silence des données sans aucun sens.
C’est comme noter l’adresse d’un ami, puis il déménage dans une maison plus grande. Votre note a toujours l’air correcte, mais elle mène à un bâtiment vide. Rust refuse de compiler le code plutôt que de vous laisser visiter le bâtiment vide.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : finir de lire avant d’écrire
Section intitulée « Solution 1 : finir de lire avant d’écrire »Commencez par celle-ci. Depuis Rust 2018, un emprunt se termine à sa dernière utilisation, pas à l’accolade fermante. C’est ce qu’on appelle les non-lexical lifetimes. Il suffit donc de finir d’utiliser la référence avant de modifier le vecteur :
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; println!("Premier : {first}"); // dernière utilisation de `first` : l'emprunt se termine ici v.push(4); // ✅ plus personne ne lit println!("Maintenant : {:?}", v);}Sortie :
Premier : 1Maintenant : [1, 2, 3, 4]Solution 2 : copier la valeur
Section intitulée « Solution 2 : copier la valeur »Choisissez-la quand vous avez besoin de l’ancienne valeur après la modification. Prenez une copie au lieu d’une référence. Pour des nombres, enlevez simplement le &. Pour une String, utilisez .clone() :
fn main() { let mut queue = vec![String::from("deploy"), String::from("test")]; let next = queue[0].clone(); // notre propre copie, pas une référence queue.push(String::from("release")); println!("Prochaine étape : {next}"); println!("File : {:?}", queue);}Sortie :
Prochaine étape : deployFile : ["deploy", "test", "release"]Avec let first = v[0]; sur un Vec<i32>, vous obtenez une copie gratuitement, parce que i32 est Copy.
Solution 3 : utiliser iter_mut ou retain pour modifier sur place
Section intitulée « Solution 3 : utiliser iter_mut ou retain pour modifier sur place »Choisissez-la quand vous avez écrit une boucle comme for p in prices.iter() puis essayé prices.remove(i) ou modifié des éléments à l’intérieur. Ça donne aussi E0502. La bibliothèque standard a des méthodes faites exactement pour ça :
fn main() { let mut prices = vec![120, 45, 300, 80];
// modifier chaque élément sur place for p in prices.iter_mut() { *p = *p * 9 / 10; // 10 % de réduction }
// retirer les éléments dont on ne veut pas prices.retain(|&p| p >= 50);
println!("{:?}", prices);}Sortie :
[108, 270, 72]iter_mut vous donne un &mut par élément, il n’y a donc jamais qu’un seul rédacteur à la fois. retain fait le tri à votre place, sans risque, en un seul passage. Voir Les vecteurs et Les itérateurs.
Solution 4 : rassembler les changements, puis les appliquer
Section intitulée « Solution 4 : rassembler les changements, puis les appliquer »Choisissez-la quand vous devez ajouter des éléments en fonction de ce que vous lisez. Lisez et rassemblez d’abord, puis écrivez dans une étape séparée :
fn main() { let mut scores = vec![10, 25, 7, 40]; let bonuses: Vec<i32> = scores.iter().filter(|&&s| s > 20).map(|s| s * 2).collect(); scores.extend(bonuses); // l'emprunt en lecture est terminé, on peut écrire println!("{:?}", scores);}Sortie :
[10, 25, 7, 40, 50, 80]Le Vec supplémentaire coûte un peu de mémoire, mais le code est clair et visiblement correct. C’est généralement un bon compromis.
Solution 5 : utiliser des indices
Section intitulée « Solution 5 : utiliser des indices »Gardez-la en dernier recours, quand les autres solutions ne conviennent pas. Un indice n’est qu’un nombre, pas un emprunt, donc il ne bloque pas les modifications :
fn main() { let mut scores = vec![10, 25, 7]; for i in 0..scores.len() { if scores[i] > 20 { scores.push(scores[i] * 2); } } println!("{:?}", scores);}Sortie :
[10, 25, 7, 50]0..scores.len() est calculé une seule fois, avant le début de la boucle, donc les nouveaux éléments ne sont pas parcourus. Faites attention quand vous retirez des éléments par indice : les positions se décalent, et vous pouvez sauter des éléments ou provoquer un panic. Pour retirer, retain (solution 3) est plus sûr.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Ajouter à un Vec pendant qu’on le parcourt. for s in &scores { scores.push(..) } emprunte scores pendant toute la boucle. Correction : rassemblez d’abord et faites extend ensuite (solution 4), ou bouclez par indice (solution 5).
Appeler une méthode &mut self pendant qu’on parcourt un champ. C’est très courant dans les blocs impl :
fn audit(&mut self) { for item in &self.items { self.record(item); // ❌ cannot borrow `*self` as mutable because it is also borrowed as immutable }}self.record emprunte tout self en mutable, y compris self.items. Correction : touchez directement l’autre champ (self.log.push(item.clone())), car Rust autorise l’emprunt de deux champs différents en même temps. Ou clonez d’abord les éléments. Voir Les structs.
Garder une valeur d’une HashMap pendant une insertion. let apples = stock.get("apples").unwrap();, puis stock.insert(..), puis une utilisation de apples donne E0502. Correction : copiez la valeur avec let apples = *stock.get("apples").unwrap(); ou .copied(). Voir HashMap.
Erreurs liées
Section intitulée « Erreurs liées »- E0499 : deux emprunts mutables en même temps. La même idée, avec deux rédacteurs au lieu d’un lecteur et d’un rédacteur.
- E0382 : vous avez déplacé une valeur, puis vous l’avez utilisée. Apparaît souvent quand on corrige E0502 en déplaçant au lieu d’emprunter.
- E0106 : un spécificateur de lifetime manquant sur une fonction qui renvoie une référence.
- Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Pourquoi let first = &v[0]; v.push(4); println!("{first}"); ne compile-t-il pas ?
2. Vous voulez retirer tous les éléments inférieurs à 50 d'un Vec<i32>. Quelle est la correction la plus propre pour E0502 ?
3. Quand un emprunt immuable se termine-t-il en Rust moderne ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0502 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
- Storing Lists of Values with Vectors — The Rust Book (en anglais) : contient exactement cet exemple
let first = &v[0]; v.push(..)et explique pourquoi il échoue. - References and Borrowing — The Rust Book (en anglais) : la règle « plusieurs lecteurs ou un seul rédacteur » en détail.
- Non-lexical lifetimes — The Edition Guide (en anglais) : pourquoi un emprunt se termine maintenant à sa dernière utilisation et non à l’accolade fermante.
Vec— documentation de la bibliothèque standard (en anglais) :retain,iter_mut,extendet les autres méthodes qui permettent de modifier un vecteur sans risque.