Aller au contenu

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.

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.

Le compilateur montre deux endroits :

  1. La ligne 2, marquée first assignment to score (première affectation de score) : c’est là que score a reçu sa valeur. Comme la ligne dit let et pas let mut, cette valeur est définitive.
  2. 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.

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.

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 : 10
Nouveau score : 15
Compteur : 3
▶ Essayez par vous-même

Le 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 : 50
Valeur lue : 43
▶ Essayez par vous-même

C’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 3
▶ Essayez par vous-même

Remarquez 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.

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.

  • 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, as p is 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

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