Erreur Rust E0382 : borrow of moved value (valeur déplacée)
E0382 veut dire que vous avez donné une valeur (vous l’avez déplacée, « moved » en anglais) et que vous avez ensuite essayé de vous en servir à nouveau. C’est l’erreur la plus fréquente de la première semaine avec Rust, et elle se corrige presque toujours en une ligne. Cette page vous montre d’abord la solution, puis vous explique ce qui se passe. 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[E0382]: borrow of moved value: `name` --> src/main.rs:8:25 |6 | let name = String::from("Ferris"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait7 | greet(name); // name moves into greet here | ---- value moved here8 | println!("Goodbye, {name}!"); // ❌ name is gone | ^^^^ value borrowed here after move |note: consider changing this parameter type in function `greet` to borrow instead if owning the value isn't necessary --> src/main.rs:1:16 |1 | fn greet(name: String) { | ----- ^^^^^^ this parameter takes ownership of the valuehelp: consider cloning the value if the performance cost is acceptable |7 | greet(name.clone()); // name moves into greet here | ++++++++Le compilateur vous dit : « emprunt d’une valeur déplacée : name ». Il vous suggère aussi deux pistes : faire en sorte que greet emprunte la valeur au lieu de la posséder, ou la cloner.
Voici le programme qui la provoque :
fn greet(name: String) { println!("Hello, {name}!");}
fn main() { let name = String::from("Ferris"); greet(name); // name moves into greet here println!("Goodbye, {name}!"); // ❌ name is gone}(Les commentaires disent : name est déplacée dans greet ici ; à la ligne suivante, name n’existe plus.)
Vous verrez peut-être aussi la formulation « use of moved value » (utilisation d’une valeur déplacée). C’est la même erreur. Rust dit « borrow » quand la ligne suivante se contente de lire la valeur (comme println!) et « use » quand elle essaie de la déplacer encore une fois (comme let other = name;).
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Lisez les trois étiquettes de haut en bas. Elles racontent toute l’histoire :
- Ligne 6 :
nameest uneString, etStringn’est pasCopy. La passer quelque part la déplace donc. - Ligne 7 :
greet(name)déplace la chaîne dans la fonction. C’est maintenantgreetqui la possède, et la chaîne est libérée à la fin degreet. - Ligne 8 : vous essayez de relire
name, maismainne la possède plus.
Imaginez que vous donnez votre seule clé de maison à un ami. Une fois la clé donnée, vous ne pouvez plus ouvrir la porte avec, même si vous vous souvenez très bien de l’avoir eue.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »En Rust, chaque valeur a exactement un seul propriétaire (c’est l’ownership, la possession). Quand le propriétaire disparaît, la valeur est nettoyée. C’est comme ça que Rust libère la mémoire sans ramasse-miettes (garbage collector). Les règles sont expliquées à la page L’ownership.
Si deux variables possédaient la même String, toutes les deux essaieraient de libérer la même mémoire à la fin. C’est ce qu’on appelle un « double free », un vrai bug qui fait planter les programmes en C et en C++. Rust l’empêche en faisant en sorte que l’affectation et les appels de fonction déplacent la valeur : il n’y a donc toujours qu’un seul propriétaire.
Les petits types simples comme i32, bool et char sont Copy : les passer crée une copie bon marché, et vous ne verrez jamais E0382 avec eux. Les types qui possèdent de la mémoire sur le tas, comme String, Vec et la plupart des structs, sont déplacés à la place.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : emprunter avec &
Section intitulée « Solution 1 : emprunter avec & »Choisissez-la quand la fonction a seulement besoin de regarder la valeur. C’est la bonne solution la plupart du temps. Changez le paramètre en référence et passez &name :
fn greet(name: &str) { println!("Bonjour, {name} !");}
fn main() { let name = String::from("Ferris"); greet(&name); // on la prête, on ne la donne pas println!("Au revoir, {name} !"); // ✅ elle est toujours à nous}Sortie :
Bonjour, Ferris !Au revoir, Ferris !&String marcherait aussi, mais &str est le choix habituel, car il accepte aussi les littéraux de chaîne. Voir Le borrowing (l’emprunt) et Les chaînes de caractères. Si la fonction doit modifier la valeur, utilisez plutôt &mut.
Solution 2 : cloner la valeur
Section intitulée « Solution 2 : cloner la valeur »Choisissez-la quand l’autre partie du code a vraiment besoin de sa propre copie, par exemple pour la ranger dans une struct ou un Vec, et que vous avez encore besoin de l’original. .clone() crée une copie complète et indépendante :
struct Player { name: String, score: u32,}
fn main() { let name = String::from("Ferris"); let player = Player { name: name.clone(), score: 0 }; println!("{} commence avec {} points", player.name, player.score); println!("Nom d'origine : {name}");}Sortie :
Ferris commence avec 0 pointsNom d'origine : FerrisSoyons honnêtes : cloner une petite chaîne ou un petit Vec ne coûte presque rien, et c’est tout à fait acceptable, surtout pendant que vous apprenez. Ne vous en souciez que si vous clonez de grosses données dans une boucle très sollicitée. Si un emprunt (solution 1) suffit, préférez-le.
Solution 3 : dériver Copy pour les petits types
Section intitulée « Solution 3 : dériver Copy pour les petits types »Choisissez-la pour les petites structs composées uniquement de valeurs simples, comme des coordonnées, des couleurs ou des identifiants. Ajoutez #[derive(Clone, Copy)] et la struct sera copiée au lieu d’être déplacée :
#[derive(Debug, Clone, Copy)]struct Point { x: i32, y: i32,}
fn show(p: Point) { println!("({}, {})", p.x, p.y);}
fn main() { let start = Point { x: 0, y: 3 }; show(start); // copie start show(start); // ✅ ça marche encore println!("{:?}", start);}Sortie :
(0, 3)(0, 3)Point { x: 0, y: 3 }Copy ne fonctionne que si chaque champ est lui aussi Copy. Une struct avec un champ String ou Vec ne peut pas être Copy : utilisez Clone et appelez .clone() dans ce cas. Pour en savoir plus, voir Les traits courants.
Solution 4 : rendre l’ownership
Section intitulée « Solution 4 : rendre l’ownership »Choisissez-la quand une fonction prend une valeur, la modifie, et que vous voulez récupérer le résultat. La fonction renvoie la valeur, et vous la stockez à nouveau :
fn add_signature(mut letter: String) -> String { letter.push_str("\n-- Ferris"); letter // on la rend}
fn main() { let letter = String::from("Cher Rustacé,"); let letter = add_signature(letter); // on la récupère println!("{letter}");}Sortie :
Cher Rustacé,-- FerrisCette méthode convient bien aux fonctions de style « builder ». Pour une simple modification sur place, fn add_signature(letter: &mut String) est souvent plus facile à lire.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Une boucle for sur un Vec. for task in tasks déplace tout le vecteur dans la boucle, vous ne pouvez donc plus utiliser tasks ensuite :
for task in tasks { println!("Doing: {task}");}println!("{} tasks done", tasks.len()); // ❌ `tasks` moved due to this implicit call to `.into_iter()`Le compilateur explique que tasks a été déplacé à cause de l’appel implicite à .into_iter(). Correction : bouclez sur un emprunt, for task in &tasks. Voir La boucle for et Les itérateurs.
Mettre une valeur dans un Vec ou une struct. names.push(name) et Order { customer } déplacent tous les deux la valeur à l’intérieur. Utiliser name ou customer ensuite échoue. Correction : relisez-la depuis son nouvel emplacement (names[0], order.customer), faites le reste de votre travail avant le déplacement, ou ajoutez un .clone().
Déplacer dans une boucle. Si log.push(message) se trouve dans une boucle, le premier tour déplace message et le deuxième tour n’a plus rien. Le compilateur dit value moved here, in previous iteration of loop (valeur déplacée ici, lors d’un tour précédent de la boucle). Correction : ajoutez message.clone(), ou créez la valeur à l’intérieur de la boucle pour que chaque tour en ait une neuve.
Les threads et les closures move. thread::spawn(move || ...) déplace chaque variable capturée dans le nouveau thread. Correction : clonez les données avant de lancer le thread, ou partagez-les avec Arc. Voir La concurrence et L’état partagé.
Erreurs liées
Section intitulée « Erreurs liées »- E0502 : vous avez emprunté une valeur, puis essayé de la modifier alors que l’emprunt servait encore.
- E0499 : deux emprunts mutables de la même valeur en même temps.
- E0106 : un spécificateur de lifetime (durée de vie) manquant, souvent quand vous renvoyez une référence pour éviter un déplacement.
- E0308 : types incompatibles, par exemple quand vous passez une
Stringlà où&strest attendu après être passé à un emprunt. - Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Vous appelez save(report), où report est une String, puis vous affichez report. Pourquoi obtenez-vous E0382 ?
2. Une fonction a seulement besoin de lire un Vec<String>. Quelle est généralement la meilleure correction ?
3. Quelle struct peut utiliser #[derive(Clone, Copy)] ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0382 — Rust error codes index (en anglais) : l’explication officielle de cette erreur, avec d’autres exemples.
- What Is Ownership? — The Rust Book (en anglais) : le chapitre qui explique pas à pas les déplacements,
CloneetCopy. - References and Borrowing — The Rust Book (en anglais) : comment
&vous permet d’utiliser une valeur sans la déplacer. - Ownership and moves — Rust by Example (en anglais) : de courts exemples exécutables de déplacements complets et partiels.
- Le trait
Copy— documentation de la bibliothèque standard (en anglais) : quels types sontCopy, et la différence entreCopyetClone.