Introduction : pourquoi Copy et Clone ne sont pas la même chose ?
\n\nQuiconque commence à apprendre Rust rencontre tôt ou tard deux traits mystérieux : Copy et Clone. À première vue, ils font la même chose : permettre d'obtenir un duplicata d'une valeur. Mais le diable, comme toujours, se cache dans les détails.
Imaginez que vous écrivez un programme et que vous passez une valeur à une fonction. En C++, ce serait une copie (ou un déplacement, si vous avez de la chance). En Python, simplement une autre référence à l'objet. Mais en Rust... Rust vous oblige à décider explicitement : "Je veux que cette valeur soit copiée" ou "Je veux la cloner en étant conscient du coût".
\n\nC'est ici que se trouve la frontière entre Copy et Clone. Le premier est une "copie légère bit par bit". Le second est un "duplicata conscient, potentiellement coûteux".
Analysons avec des exemples quand et quoi utiliser, pour que votre code Rust soit non seulement correct, mais aussi efficace.
\n\nL'essentiel en bref : Copy vs Clone
\n\nQu'est-ce que Clone ?
\n\nClone est un trait qui vous donne la méthode .clone(). En l'appelant, vous dites explicitement au compilateur : "Je sais que cela peut être coûteux, mais j'ai besoin d'une copie indépendante".
Caractéristiques de Clone :
- \n
- Peut être implémenté pour n'importe quel type \n
- Nécessite un appel explicite à
.clone()\n - Peut effectuer une copie profonde (par exemple pour
VecouString) \n - Ne change pas la sémantique de possession — après le clonage, vous avez deux valeurs indépendantes \n
Qu'est-ce que Copy ?
\n\nCopy est un trait "marqueur" (sans méthodes). Si un type implémente Copy, le compilateur peut simplement copier ses bits lors d'une affectation ou d'un passage à une fonction. Cela ne nécessite pas d'appeler .clone().
Caractéristiques de Copy :
- \n
- Fonctionne uniquement pour les types simples qui peuvent être copiés en toute sécurité bit par bit \n
- Ne nécessite pas d'appel explicite — la copie se fait automatiquement \n
- Le type doit toujours implémenter
Clone(c'est un supertrait) \n - La valeur d'origine reste valide après la copie \n
Exemples pratiques : comment cela fonctionne dans le code
\n\nExemple 1 : Types de base (Copy)
\n\nLes entiers, les nombres à virgule flottante, les booléens — tous implémentent Copy :
fn main() {\n let x = 42;\n let y = x; // x est copié dans y, x est toujours accessible\n \n println!("x = {}, y = {}", x, y); // Fonctionne !\n}\n\nIci, x est un i32, qui implémente Copy. Donc lors de l'affectation y = x, une copie a lieu et x reste valide.
Exemple 2 : String (Clone uniquement)
\n\nPar contre, String est un type complexe qui stocke des données sur le tas. Il implémente Clone, mais pas Copy :
fn main() {\n let s1 = String::from("Bonjour, Rust !");\n let s2 = s1; // s1 est déplacé dans s2\n \n // println!("{}", s1); // Erreur ! s1 ne possède plus les données\n \n let s3 = s2.clone(); // Voilà comment faire — clonage explicite\n println!("s2 = {}, s3 = {}", s2, s3); // Fonctionne !\n}\n\nPourquoi String ne peut-il pas être Copy ? Parce qu'une copie bit par bit de String entraînerait deux pointeurs vers la même mémoire sur le tas. Lors du drop des deux valeurs — double libération de mémoire. Clone crée quant à lui une copie indépendante complète.
Exemple 3 : Type personnalisé avec Copy
\n\nVous pouvez rendre votre type Copy s'il est composé uniquement de types Copy :
#[derive(Debug, Copy, Clone)]\nstruct Point {\n x: f64,\n y: f64,\n}\n\nfn main() {\n let p1 = Point { x: 1.0, y: 2.0 };\n let p2 = p1; // Copié, p1 reste\n \n println!