Move, Copy et Clone
Lecture 5 min. âą
Table des matiĂšres
Move
Lorsquâune fonction dĂ©sire obtenir lâownership dâune variable.
On vient déplacer la variable du contexte appelant vers le contexte appelé.
Prenons une structure Toto.

On dĂ©clare une fonction function_1, celle-ci ne fait rien Ă part prendre lâownership de la variable passĂ© en paramĂštre.

On déclare dans un main, une variable toto de type Toto.

Lorsque lâon passe en paramĂštre la variable toto Ă la fonction function_1, on dit que lâon dĂ©place ou move la variable vers function_1.

Câest comme si vous aviez un carton dans une maison 1 et que vous le dĂ©placiez dans une maison 2.

Et donc trĂšs logiquement si on rĂ©itĂšre lâexpĂ©rience, ça coince.

Pourquoi, ça coince ?
Et bien, votre carton nâa pas le don dâubiquitĂ© (ce mot est cadeau pour vous đ). Autrement dit, elle ne peut pas Ă la fois ĂȘtre dans la maison main et dans la maison function_1.
Donc si les déménageurs arrivent, ils ne trouveront pas le carton dans la maison main! Et ne pourront pas le déplacer vers la maison function_1*

Clone
Pour remédier à cette situation, nous allons devoir implémenter un trait.
Celui-ci ce nomme Clone.
Il ne dispose que dâune fonction Ă implĂ©menter.
trait Clone {
fn clone(&self) -> Self
}Ce trait renvoit une duplication de lui-mĂȘme, cela impliquant que chaque champ de la structure qui lâimplĂ©mente doivent eux-aussi ĂȘtre clonable.
Pour notre structure Toto ce nâest pas un soucis.
impl Clone for Toto {
fn clone(&self) {
*self
}
}On déréférence le self et on renvoit une copie.
Un clone est déclenché de maniÚre consciente et explicite par le développeur.
fn main() {
let toto = Toto;
let toto_clone = toto.clone(); // l'opération de clone se fait ici
}Si ce que vous clonez est gros, cela peut avoir des conséquences sur les performances de votre programme.
struct BigData { data: [u8; 10000000000] }Chaque
.clone()aura pour effet de dupliquer chaque valeur du tableau.Si les opĂ©rations de clonage sont trop frĂ©quentes, cela risque dâengorger votre programme.
Il est possible de se passer de lâimplĂ©mentation du trait Clone en passant par la dĂ©rivation.
#[derive(Clone)]
struct Toto;Nous avons maintenant des armes pour travailler. đ

Il faut voir le clone comme une machine qui prend en entré une variable clonable, en réalise une deuxiÚme version exacte et rend la premiÚre version.

Notre scénario avec les déménageurs va donc se dérouler en deux temps:
PremiĂšrement, on clone le carton toto en un deuxiĂšme carton toto_clone.

Puis on demande Ă lâĂ©quipe 1 de dĂ©mĂ©nager le carton Ă©tiquetĂ© toto_clone et Ă lâĂ©quipe 2 de dĂ©mĂ©nager le carton Ă©tiquetĂ© toto.

Chaque Ă©quipe trouve son carton, tout le monde est content. đ
Comme tout Ă lâheure,
totoayant Ă©tĂ© dĂ©placĂ© dansfunction_1*, il nâexiste plus dansmain.Cela implique quâil nâest plus clonable dans
mainet nâest pas dĂ©plaçable dans une troisiĂšme maisonfunction_1**
Copy
Il existe un deuxiĂšme mĂ©canisme que nous avons dâailleurs utilisĂ© sans nous en rendre compte, il sâagit de la copie.
Pour pouvoir bĂ©nĂ©ficier de cette capacitĂ© une variable doit ĂȘtre copiable.
Cette attribut est fourni par le trait Copy.
Le trait Copy est un super trait de Clone. Cela implique que pour implémenter Copy une structure doit obligatoirement implémenter Clone.
Le trait Copy est ce quâon appelle un Marker, un trait sans implĂ©mentation.
#[derive(Clone)]
struct Toto;
impl Copy for Toto {}Il est également possible de passer la dérivation.
#[derive(Clone, Copy)]
struct Toto;Celle-ci est fourni par le langage et ne peut pas ĂȘtre dĂ©fini par le dĂ©veloppeur.
Les types primimitifs sont tous copiables. A lâexeception des chaĂźne de caractĂšres qui sont en fait des rĂ©fĂ©rences.
Une structure composée de types primitifs est donc copiable.
#[derive(Clone, Copy)]
struct Data {
number: u64,
float: f64,
boolean: bool,
array: [u8; 1024]
}Quel super-pouvoirs cela nous octroie ?
Et bien, plus besoin de demander explicitement un clone via .clone() tout se fait magiquement !
fn main() {
let toto = Toto;
let toto_copy = toto; // la variable est ici copié
}Ce qui implique que lâon peut Ă©crire ceci
fn main() {
let toto = Toto;
function_1(toto); // toto est copié
function_1(toto); // toto est copié
function_1(toto); // toto est copié
function_1(toto);
// etc ...
function_1(toto);
// toto est toujours présent dans main !!
}Si on reprend la scenette des dĂ©mĂ©nageurs, câest comme si Ă chaque fois quâon prenait un carton, celui-ci se dupliquait de lui-mĂȘme.
Ce qui permet Ă chaque maison function_1 dâavoir sa propre copie de toto.
Autre avantage, le toto originel ne quitte jamais la maison main!

Il est donc possible de dĂ©mĂ©nager le carton toto autant de fois que lâon le dĂ©sire.
La copie Ă©tant implicite, il est possible de copier de grandes quantitĂ©e de donnĂ©es sans sâen rendre compte !
Conclusion
Si une fonction dĂ©sire lâownership dâune variable il faut dĂ©placer la variable du contexte appellant vers le contexte appelĂ©.
Une fois quâune variable est dĂ©placĂ©e dans le contexte appelĂ©, elle nâest plus disponible dans le contexte appellant.
Pour conserver la disponibilitĂ© dâune variable dans le contexte appellant, il faut dupliquer la variable.
Soit via un Clone expicite.

Soit via un Copy implicite.

Toute variable copiable est clonable mais lâinverse nâest pas vrai.
