Aller au contenu

Erreur Rust E0596 : cannot borrow as mutable (variable non déclarée mut)

E0596 signifie qu’un bout de code veut modifier une valeur (il lui faut un emprunt mutable, &mut), alors que vous lui avez seulement donné le droit de la lire. En général, vous avez oublié mut sur un let, ou une fonction prend un & alors qu’elle a besoin d’un &mut. C’est très courant, et la ligne help: du compilateur vous dit presque toujours exactement où ajouter mut. Cette page montre d’abord la solution, puis l’explique. Pour les autres erreurs de cette série, consultez Les erreurs du compilateur Rust.

error[E0596]: cannot borrow `tasks` as mutable, as it is not declared as mutable
--> src/main.rs:3:5
|
3 | tasks.push("écrire les tests"); // ❌ push doit modifier `tasks`
| ^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
2 | let mut tasks = Vec::new();
| +++

Autrement dit (rustc s’exprime toujours en anglais) : impossible d’emprunter tasks en mutable, car elle n’est pas déclarée mutable ; le compilateur propose d’ajouter mut. Voici le programme qui la provoque :

fn main() {
let tasks = Vec::new();
tasks.push("écrire les tests"); // ❌ push doit modifier `tasks`
println!("{tasks:?}");
}

Vous verrez aussi une deuxième formulation : « cannot borrow *tasks as mutable, as it is behind a & reference » (impossible d’emprunter en mutable, car c’est derrière une référence &). Même code d’erreur, cause un peu différente : là, la variable est une référence &, donc vous ne pouvez pas modifier ce qu’elle désigne. Les deux cas sont traités plus bas.

Regardez les ^^^^^ sous tasks, ligne 3. Pour ajouter un élément, Vec::push a besoin d’un emprunt mutable du vecteur : sa signature est fn push(&mut self, value: T). Mais tasks a été créé avec un simple let, il est donc en lecture seule. Rust ne peut pas fournir un &mut vers quelque chose dont vous avez dit qu’il ne changerait jamais.

La ligne help: montre la solution : let mut tasks. Les +++ marquent les trois caractères à ajouter.

Pensez à un document partagé en « lecture seule ». Vous pouvez lire chaque mot, mais le bouton « modifier » est grisé. mut (ou &mut), c’est votre façon de demander le droit de modifier.

En Rust, les variables sont immuables par défaut, et les références sont en lecture seule par défaut. Pour modifier quoi que ce soit, il faut le demander explicitement : let mut pour une variable que vous possédez, &mut pour une référence vers quelque chose que vous avez emprunté. Voir Les variables et Le borrowing.

Ça rend le code plus facile à croire. Si une fonction prend un &Vec<String>, sa signature suffit pour savoir qu’elle ne modifiera pas votre liste. Si elle prend un &mut Vec<String>, vous savez qu’elle pourrait le faire. Rien ne change dans votre dos.

C’est aussi cette règle qui rend possibles les grandes garanties de sécurité de Rust. Comme « peut modifier » est toujours écrit noir sur blanc, le compilateur peut s’assurer qu’il n’y a jamais un rédacteur et un lecteur en même temps (ce sont les erreurs E0502 et E0499). E0596 est la première étape : vous devez demander à écrire avant que quiconque vérifie si écrire est sans danger.

Choisissez cette solution quand vous possédez la valeur et qu’elle doit changer : un Vec dans lequel vous faites push, une String à laquelle vous ajoutez du texte, ou une struct dont vous appelez une méthode &mut self. C’est la bonne solution la plupart du temps.

struct Counter {
count: u32,
}
impl Counter {
fn increment(&mut self) {
self.count += 1;
}
}
fn main() {
let mut tasks = Vec::new();
tasks.push("écrire les tests");
tasks.push("corriger le bug");
println!("{tasks:?}");
let mut counter = Counter { count: 0 };
counter.increment();
counter.increment();
println!("Compteur : {}", counter.count);
}

Sortie :

["écrire les tests", "corriger le bug"]
Compteur : 2
▶ Essayez par vous-même

Sans mut sur counter, counter.increment() échoue avec cannot borrow counter as mutable. Appeler une méthode qui prend &mut self emprunte toute la variable en mutable, donc la variable doit être mut. Les méthodes qui prennent seulement &self fonctionnent très bien sans. Voir Ajouter du comportement avec des méthodes.

C’est pareil pour un paramètre que la fonction possède : fn shout(s: String) ne peut pas appeler s.push('!'), mais fn shout(mut s: String) le peut.

Solution 2 : passer le paramètre en &mut et appeler avec &mut x

Section intitulée « Solution 2 : passer le paramètre en &mut et appeler avec &mut x »

Choisissez cette solution quand une fonction doit modifier quelque chose que l’appelant possède. Cette version donne la formulation « behind a & reference » :

fn add_task(tasks: &Vec<String>, task: &str) {
tasks.push(task.to_string()); // ❌ `tasks` est une référence `&`
}
error[E0596]: cannot borrow `*tasks` as mutable, as it is behind a `&` reference
--> src/main.rs:2:5
|
2 | tasks.push(task.to_string()); // ❌ `tasks` est une référence `&`
| ^^^^^ `tasks` is a `&` reference, so it cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
1 | fn add_task(tasks: &mut Vec<String>, task: &str) {
| +++

Le compilateur explique que tasks est une référence &, donc impossible à emprunter en mutable, et propose d’en faire une référence mutable. Corrigez à trois endroits : le type du paramètre devient &mut Vec<String>, la variable de l’appelant devient let mut, et l’appel passe &mut tasks :

fn add_task(tasks: &mut Vec<String>, task: &str) {
tasks.push(task.to_string());
}
fn main() {
let mut tasks = Vec::new();
add_task(&mut tasks, "écrire les tests");
add_task(&mut tasks, "mettre à jour la doc");
println!("{} tâches : {tasks:?}", tasks.len());
}

Sortie :

2 tâches : ["écrire les tests", "mettre à jour la doc"]
▶ Essayez par vous-même

Si vous changez le paramètre mais passez toujours &tasks, vous obtenez E0308 à la place, avec la note types differ in mutability (les types diffèrent par leur mutabilité). Ça veut simplement dire : écrivez aussi &mut tasks à l’appel. Écrire &mut à l’appel est voulu : quiconque lit main voit que add_task peut modifier tasks.

Choisissez cette solution quand une closure (fonction anonyme) modifie une variable qu’elle a capturée. Une telle closure change son propre état à chaque appel, donc l’appeler demande un emprunt mutable de la closure elle-même :

fn main() {
let mut clicks = 0;
let click = || clicks += 1;
click(); // ❌ cannot borrow `click` as mutable
click();
println!("Clics : {clicks}");
}
error[E0596]: cannot borrow `click` as mutable, as it is not declared as mutable
--> src/main.rs:3:9
|
3 | let click = || clicks += 1;
| ^^^^^ ------ calling `click` requires mutable binding due to mutable borrow of `clicks`
| |
| not mutable
4 | click(); // ❌ cannot borrow `click` as mutable
| ----- cannot borrow as mutable
5 | click();
| ----- cannot borrow as mutable
|
help: consider changing this to be mutable
|
3 | let mut click = || clicks += 1;
| +++

Le compilateur précise qu’appeler click exige une variable mutable, parce que la closure emprunte clicks en mutable. La variable clicks est déjà mut. Ce qui manque, c’est mut sur la closure :

fn main() {
let mut clicks = 0;
let mut click = || clicks += 1;
click();
click();
click();
println!("Clics : {clicks}");
}

Sortie :

Clics : 3
▶ Essayez par vous-même

Ça surprend beaucoup de monde, parce que la closure n’a pas « l’air » de changer. Mais elle contient un &mut clicks, c’est donc une closure FnMut, et appeler une FnMut demande mut. Voir Les closures qui modifient les variables capturées et Fn, FnMut et FnOnce.

Solution 4 : utiliser iter_mut() ou &mut dans une boucle

Section intitulée « Solution 4 : utiliser iter_mut() ou &mut dans une boucle »

Choisissez cette solution quand vous parcourez une collection et voulez modifier chaque élément. .iter() et for x in &list fournissent des références &, qui sont en lecture seule :

for name in names.iter() {
name.push('!'); // ❌ cannot borrow `*name` as mutable, as it is behind a `&` reference
}

Passez à .iter_mut() ou à &mut names (et rendez names lui-même mut) :

fn main() {
let mut names = vec![String::from("ferris"), String::from("corro")];
for name in names.iter_mut() {
name.push('!');
}
println!("{names:?}");
let mut prices = vec![10, 20, 30];
for price in &mut prices {
*price += 5;
}
println!("{prices:?}");
}

Sortie :

["ferris!", "corro!"]
[15, 25, 35]
▶ Essayez par vous-même

Le compilateur le propose aussi : help: use mutable method ... names.iter_mut(). C’est pareil pour les closures dans une chaîne d’itérateurs : words.iter().for_each(|w| w.push('!')) échoue, words.iter_mut().for_each(|w| w.push('!')) fonctionne. Voir Les itérateurs.

Construire un Vec ou une String petit à petit. let list = Vec::new(); puis list.push(...), ou let s = String::new(); puis s.push_str(...). Solution : let mut. Si vous avez tous les éléments dès le départ, let list = vec![a, b, c]; n’a pas du tout besoin de mut.

Appeler une méthode qui modifie une struct. account.deposit(50), où deposit prend &mut self, échoue si vous n’avez pas écrit let mut account. Solution : let mut account = Account::new();. Voir Les structs.

Des fonctions utilitaires qui prennent & par habitude. Vous écrivez fn reset(board: &Board), puis vous essayez de modifier board à l’intérieur. Solution : changez en board: &mut Board et appelez reset(&mut board). Une bonne habitude : écrivez & quand la fonction ne fait que lire, &mut quand elle modifie.

Modifier une valeur dans une HashMap. if let Some(v) = map.get("key") { v.push(..) } échoue, parce que get renvoie une référence &. Solution : utilisez map.get_mut("key"), ou l’API entry. Voir Les HashMap.

  • E0384 : cannot assign twice to immutable variable (affectation en double d’une variable immuable). Le même mut oublié, mais pour x = ... ou x += 1 au lieu d’un appel de méthode.
  • E0594 : impossible d’affecter une valeur à un champ ou à travers une référence &, par exemple p.hp = 5; quand p n’est pas mut, ou *x += 1 dans for x in &v. Mêmes solutions : let mut, ou &mut.
  • E0308 : mismatched types (types incompatibles), types differ in mutability, quand vous passez &x à une fonction qui attend &mut x.
  • E0502 : vous avez obtenu un accès &mut, mais quelque chose d’autre lit encore la valeur.
  • E0499 : deux emprunts &mut de la même valeur en même temps.
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.

Petit quiz

1. let scores = Vec::new(); scores.push(10); donne E0596. Quelle est la solution ?

2. Une fonction fn add(list: &Vec<u32>) appelle list.push(1). Que changez-vous ?

3. let mut total = 0; let add = |x| total += x; add(5); échoue. Pourquoi ?

Score : 0 / 3