Erreur Rust E0384 : cannot assign twice to immutable variable (variable non mut)
E0384 signifie que vous avez donné une valeur à une variable, puis essayé de lui en donner une autre, alors que la variable n’a pas été déclarée avec mut. En Rust, une variable ne peut pas changer, sauf si vous le demandez. Presque tout le monde rencontre cette erreur dès sa première heure de Rust, et la solution tient souvent en trois lettres : mut. Cette page montre d’abord la solution, puis explique pourquoi Rust fonctionne ainsi. 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[E0384]: cannot assign twice to immutable variable `score` --> src/main.rs:4:5 |2 | let score = 10; | ----- first assignment to `score`3 | println!("Score : {score}");4 | score = 15; // ❌ score n'est pas mut | ^^^^^^^^^^ cannot assign twice to immutable variable |help: consider making this binding mutable |2 | let mut score = 10; | +++Autrement dit (rustc s’exprime toujours en anglais) : impossible d’affecter deux fois une valeur à la variable immuable score, et le compilateur propose de la rendre mutable. Voici le programme qui la provoque :
fn main() { let score = 10; println!("Score : {score}"); score = 15; // ❌ score n'est pas mut println!("Nouveau score : {score}");}Si la variable est un paramètre de fonction, le message change un peu : « cannot assign to immutable argument » (impossible d’affecter une valeur à un argument immuable). C’est le même code d’erreur et la même idée.
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Le compilateur montre deux endroits :
- La ligne 2, marquée
first assignment to score(première affectation descore) : c’est là quescorea reçu sa valeur. Comme la ligne ditletet paslet mut, cette valeur est définitive. - La ligne 4, soulignée par
^^^^: c’est là que vous essayez d’y mettre une nouvelle valeur. Rust refuse.
La partie help: donne déjà la solution : let mut score = 10;. Les +++ marquent les trois caractères à ajouter.
Voyez let comme écrire au stylo, et let mut comme écrire au crayon. Les deux conviennent. Mais si vous avez écrit au stylo, vous ne pouvez pas gommer ensuite.
La même règle s’applique à +=, -=, *= et compagnie. count += 1 est aussi une affectation, donc il faut aussi mut.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »En Rust, toute variable est immuable par défaut. Pour pouvoir la modifier, vous devez l’indiquer avec mut. C’est expliqué sur la page Les variables.
Ça paraît strict, mais ça vous aide beaucoup. Quand vous lisez let total = ..., vous savez que total garde cette valeur jusqu’à la fin de sa portée. Pas besoin de parcourir le reste de la fonction pour vérifier si quelque chose l’a modifiée. Et quand vous voyez let mut, le signal est clair : « attention, celle-ci change ».
Ça attrape aussi de vrais bugs. Si vous vouliez modifier une copie mais avez écrit dans l’original par erreur, ou réutilisé un nom sans le vouloir, le compilateur vous arrête. Et Rust vous avertit quand une variable mut ne change jamais (variable does not need to be mutable, « la variable n’a pas besoin d’être mutable »), si bien que le signal reste fiable dans les deux sens.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : ajouter mut
Section intitulée « Solution 1 : ajouter mut »Choisissez cette solution quand la variable doit vraiment changer au fil du temps : un compteur, un total, un score, un état. C’est la bonne solution la plupart du temps, et c’est exactement ce que propose le compilateur.
fn main() { let mut score = 10; println!("Score : {score}"); score = 15; // ✅ autorisé maintenant println!("Nouveau score : {score}");
let mut count = 0; for _ in 0..3 { count += 1; } println!("Compteur : {count}");}Sortie :
Score : 10Nouveau score : 15Compteur : 3Le compteur est le cas classique : let count = 0; suivi de count += 1; dans une boucle donne E0384 sur la ligne du +=. Ajouter mut règle le problème. Voir Rendre une variable modifiable avec mut.
Solution 2 : utiliser le shadowing pour une nouvelle valeur
Section intitulée « Solution 2 : utiliser le shadowing pour une nouvelle valeur »Choisissez cette solution quand vous ne voulez pas vraiment modifier la variable. Vous voulez une nouvelle valeur calculée à partir de l’ancienne, souvent en quelques étapes, et qui ne bouge plus ensuite. Écrivez de nouveau let avec le même nom :
fn main() { let price = 20; let price = price + 5; // un nouveau `price`, construit à partir de l'ancien let price = price * 2; println!("Prix final : {price}");
let input = " 42 "; let input = input.trim(); // toujours du texte let input: u32 = input.parse().unwrap(); // maintenant un nombre println!("Valeur lue : {}", input + 1);}Sortie :
Prix final : 50Valeur lue : 43C’est ce qu’on appelle le shadowing (masquage) : chaque let crée une toute nouvelle variable qui cache l’ancienne. Ça apporte deux avantages. Le price final est immuable, donc rien ne peut le modifier par accident plus tard. Et le type peut changer, comme input qui passe de &str à u32, ce que mut ne permet pas. Plus de détails dans Le shadowing : réutiliser le même nom.
Une règle simple : si la valeur change encore et encore (dans une boucle, au fil du temps), utilisez mut. Si vous préparez une valeur en quelques étapes, le shadowing se lit très bien.
Solution 3 : ajouter mut à un paramètre de fonction
Section intitulée « Solution 3 : ajouter mut à un paramètre de fonction »Choisissez cette solution quand une fonction veut modifier sa propre copie d’un argument. Les paramètres sont eux aussi immuables, donc ceci échoue :
fn countdown(n: u32) { while n > 0 { println!("{n}..."); n -= 1; // ❌ cannot assign to immutable argument `n` } println!("Décollage !");}error[E0384]: cannot assign to immutable argument `n` --> src/main.rs:4:9 |4 | n -= 1; // ❌ cannot assign to immutable argument `n` | ^^^^^^ cannot assign to immutable argument |help: consider making this binding mutable |1 | fn countdown(mut n: u32) { | +++Le compilateur indique qu’on ne peut pas modifier l’argument immuable n et propose mut n. Placez mut devant le nom du paramètre :
fn countdown(mut n: u32) { while n > 0 { println!("{n}..."); n -= 1; } println!("Décollage !");}
fn main() { let start = 3; countdown(start); println!("start vaut toujours {start}");}Sortie :
3...2...1...Décollage !start vaut toujours 3Remarquez que start, dans main, n’a pas changé. mut n rend seulement la copie propre à la fonction modifiable ; l’appelant n’a pas besoin de mut et n’est pas touché. Si vous voulez que la fonction modifie la variable de l’appelant, prenez plutôt un &mut u32 et passez &mut start. Voir Les fonctions et Le borrowing.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Modifier la variable d’une boucle for. La variable de boucle est une nouvelle variable immuable à chaque tour :
for i in 0..5 { if i == 2 { i += 1; // ❌ cannot assign twice to immutable variable `i` } println!("{i}");}Solution : on veut rarement modifier le compteur de boucle lui-même. Sautez un tour avec continue, utilisez .step_by(2), ou, si vous avez besoin d’un contrôle total, passez à une boucle while avec un let mut i. (for mut i in 0..5 compile, mais ne modifie que la copie de ce tour ; ça ne saute rien.) Voir break et continue et Les boucles while.
Déclarer d’abord, affecter plus tard. let total; sans valeur est autorisé, et vous pouvez l’affecter une seule fois. Une deuxième affectation donne E0384 :
let total;total = 5;total = 6; // ❌ cannot assign twice to immutable variable `total`Solution : si la valeur change vraiment, utilisez let mut total. Si vous vouliez seulement choisir une valeur, calculez-la en une seule expression : let total = if big_order { 6 } else { 5 };. Voir if / else.
Accumuler un total. let sum = 0; suivi de sum += price; dans une boucle, c’est le E0384 le plus courant de tous. Solution : let mut sum = 0;, ou bien passez-vous de la variable et écrivez let sum: u32 = prices.iter().sum();. Voir Les itérateurs.
Erreurs liées
Section intitulée « Erreurs liées »- E0596 : cannot borrow as mutable, as it is not declared as mutable (impossible d’emprunter en mutable). Le même oubli de
mut, mais quand vous appelez une méthode qui modifie, comme.push(), au lieu d’affecter. - E0594 : cannot assign to
p.hp, aspis not declared as mutable. Vous la voyez quand vous affectez une valeur à un champ de struct (p.hp = 5;) ou quand vous écrivez à travers une référence&. Solution :let mut p, ou une référence&mut. Voir Rendre une struct mutable. - E0502 : vous essayez de modifier une valeur pendant qu’elle est empruntée en lecture.
- E0382 : vous utilisez une valeur après qu’elle a été déplacée.
- Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Vous écrivez let lives = 3; puis, plus loin, lives -= 1;. Pourquoi obtenez-vous E0384 ?
2. Vous voulez nettoyer name une seule fois (enlever les espaces et le passer en majuscules), puis ne plus jamais le modifier. Qu'est-ce qui se lit le mieux ?
3. fn halve(n: u32) fait n /= 2; et obtient E0384. Quelle correction laisse l'appelant inchangé ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0384 — Rust error codes index (en anglais) : l’explication officielle de cette erreur, avec un exemple.
- Variables and Mutability — The Rust Book (en anglais) : le chapitre sur
let,mut, les constantes et le shadowing. - Mutability — Rust by Example (en anglais) : un court exemple exécutable de
letface àlet mut. - Freezing — Rust by Example (en anglais) : comment masquer une variable mutable par une variable immuable la « gèle ».