«Use of moved value» en Rust : pourquoi cela se produit et comment vivre avec
\n\nSi vous venez tout juste de commencer à apprendre Rust, vous avez sûrement déjà rencontré le message mystérieux : error[E0382]: use of moved value. Le compilateur râle, vous vous grattez la tête, et un code logiquement parfait refuse de se compiler. Cela vous dit quelque chose ? Bienvenue dans le monde de la possession et de l'emprunt — les concepts clés de Rust qui le rendent sûr, mais qui laissent parfois même les développeurs expérimentés perplexes.
Cet article n'est pas une documentation aride, mais une analyse vivante du problème. Nous examinerons pourquoi l'erreur use of moved value se produit, avec des exemples de code concrets, et surtout, que faire pour y remédier. Vous apprendrez à penser comme le borrow checker et cesserez de lutter contre lui. C'est parti !
\n\nQu'est-ce qu'un « move » en Rust et pourquoi le compilateur est-il si strict ?
\n\nEn Rust, chaque valeur a exactement un propriétaire (owner). Lorsque vous assignez une valeur à une autre variable ou que vous la passez à une fonction, la propriété est déplacée (move). Après le déplacement, la variable d'origine devient invalide — le compilateur interdit d'y accéder. C'est la racine de l'erreur use of moved value.
\n\nRegardez cet exemple très simple :
\n\nlet s1 = String::from("bonjour");\nlet s2 = s1; // La propriété est déplacée de s1 à s2\nprintln!("{}", s1); // ERREUR : use of moved value: `s1`\n\nPourquoi cela ? String n'est pas une simple copie sur la pile. Elle stocke des données sur le tas. Si Rust copiait simplement le pointeur, lors de la libération de la mémoire, les deux variables tenteraient de la supprimer — double free. Au lieu de cela, Rust « déplace » la propriété : désormais, s2 est responsable de la mémoire, et s1 n'existe plus du point de vue du compilateur. C'est l'idée fondamentale de la sémantique de déplacement en Rust.
Scénarios typiques menant à l'erreur
\n\nL'erreur use of moved value se rencontre le plus souvent dans trois situations :
- \n
- Passer une valeur à une fonction \n
- Assigner une valeur à une autre variable \n
- Utiliser une valeur dans une boucle ou un itérateur \n
Examinons chaque cas en détail.
\n\n1. Passage à une fonction sans retour
\n\nfn print_string(s: String) {\n println!("{}", s);\n}\n\nfn main() {\n let my_string = String::from("Rust");\n print_string(my_string); // propriété déplacée\n println!("{}", my_string); // erreur ! use of moved value\n}\n\nAprès l'appel à print_string, la propriété de my_string est passée à la fonction. Lorsque la fonction se termine, s est supprimée, et my_string n'est plus valide. Le compilateur vous protège de l'utilisation d'un objet mort.
2. Assignation sans copie
\n\nNous avons déjà vu cet exemple avec String. Mais il est important de noter que tous les types ne se comportent pas ainsi. Les types simples comme i32, bool, f64 implémentent le trait Copy. Ils ne sont pas déplacés, mais copiés automatiquement.
let x = 5;\nlet y = x; // copie, pas de déplacement\nprintln!("{}", x); // fonctionne !\n\nLes types qui stockent des données sur le tas (String, Vec, Box) n'implémentent pas Copy — seulement Clone. Par conséquent, ils sont déplacés.
3. Itération sur une collection
\n\nUne autre source fréquente d'erreurs est la boucle for :
let v = vec![1, 2, 3];\nfor x in v { // v est déplacé dans l'itérateur\n println!("{}", x);\n}\nprintln!("{:?}", v); // erreur ! use of moved value\n\nLorsque vous écrivez for x in v, vous appelez en réalité into_iter(), qui consomme le vecteur. Après la boucle, v n'existe plus.
Comment corriger : 4 méthodes éprouvées
\n\nMainte