Move, Copy et Clone

Lecture 5 min. ‱


Table des matiĂšres

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, toto ayant Ă©tĂ© dĂ©placĂ© dans function_1*, il n’existe plus dans main.

Cela implique qu’il n’est plus clonable dans main et n’est pas dĂ©plaçable dans une troisiĂšme maison function_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.