Leçons du module (1/2)
Les règles de Ownership
L'Ownership (propriété) est la caractéristique la plus distinctive de Rust. Elle permet au langage de garantir la sécurité de la mémoire sans avoir besoin d'un Garbage Collector ou de vous obliger à désallouer manuellement la mémoire tas (heap) (comme en C ou C++).
Les Trois Règles de l'Ownership
La gestion de la mémoire en Rust est régie par trois règles simples, rigoureusement contrôlées par le compilateur :
- Chaque valeur en Rust a une variable appelée son propriétaire (owner).
- Il ne peut y avoir qu'un seul propriétaire à la fois.
- Lorsque le propriétaire sort de la portée (scope), la valeur est automatiquement supprimée.
Pile (Stack) vs Tas (Heap)
Pour comprendre l'ownership, il est important de savoir où résident les données :
- Pile (Stack) : stocke les données dont la taille est fixe et connue au moment de la compilation (ex. entiers, booléens). L'accès est extrêmement rapide.
- Tas (Heap) : stocke les données dont la taille est dynamique et inconnue au moment de la compilation (ex.
String). L'allocation est plus lente et nécessite un pointeur stocké sur la pile.
Le concept de "Move" (Déplacement)
Lorsque nous assignons une variable stockée dans le tas à une autre, Rust déplace (ou transfère) la propriété de la valeur, invalidant la première variable :
let s1 = String::from("ciao");
let s2 = s1; // La proprieta del testo si sposta a s2. s1 NON e piu utilizzabile!
// println!("{}", s1); // ERRORE DI COMPILAZIONE! s1 e "borrow of moved value"
println!("{}", s2); // Valido!
Ce comportement évite le problème de la "double libération de mémoire" (double free error : tenter de libérer la même mémoire deux fois à la sortie de la portée), puisque seule s2 libérera la mémoire.
Le concept de "Clone" (Copie Profonde)
Si nous avons le besoin explicite de copier l'intégralité du contenu d'une String (à la fois les pointeurs sur la pile et les données textuelles réelles dans le tas), nous pouvons recourir à la méthode .clone(). Cela duplique complètement les données sur le tas, en maintenant valides à la fois la variable d'origine et la nouvelle, au prix d'un impact sur les performances dû à la nouvelle allocation de mémoire :
let s1 = String::from("ciao");
let s2 = s1.clone(); // Copia profonda. Entrambe le variabili rimangono valide!
println!("s1: {}, s2: {}", s1, s2);
Le concept de "Copy"
Pour les types simples sauvegardés entièrement sur la pile (comme les entiers i32, les booléens bool, les caractères char), l'assignation effectue une véritable copie superficielle automatique, ainsi les deux variables restent valides :
let x = 5;
let y = x; // Copia il valore 5 nello stack. Entrambe le variabili sono utilizzabili!
println!("x: {}, y: {}", x, y); // Valido!
À toi de jouer
Déclarez une String s1 contenant le texte 'hello'. Assignez s1 à s2 (de manière à déplacer la propriété). Enfin, affichez s2 à l'écran en utilisant println!.
Afficher l'indice
Utilisez `let s2 = s1;` pour transférer la propriété à `s2` et affichez-la avec `println!('{}', s2);`.
Solution disponible après 3 tentatives
Déclarez une variable entière x ayant la valeur 5. Assignez x à y (en effectuant une copie). Enfin, affichez à la fois x et y dans la même instruction println!.
Afficher l'indice
Faites `let y = x;` et affichez ensuite les deux valeurs, ex. `println!('{} {}', x, y);`.
Solution disponible après 3 tentatives
Déclarez une String s1 ayant la valeur "Rust". Créez une copie profonde de s1 dans une nouvelle variable s2 à l'aide de la méthode clone. Enfin, affichez à la fois s1 et s2 séparés par un espace en utilisant la macro println!.
Afficher l'indice
Utilisez `let s2 = s1.clone();` pour copier en profondeur la valeur de `s1` dans `s2`, puis affichez-les avec `println!("{} {}", s1, s2);`.
Solution disponible après 3 tentatives