#5 Ownership
move, copy, drop
Qu'est-ce que l'ownership ?
L'ownership (propriété) est le concept central de Rust. C'est ce qui permet à Rust de garantir la sécurité mémoire sans ramasse-miettes. Chaque valeur en Rust a un propriétaire unique.
Quand le propriétaire sort de la portée (scope), la valeur est automatiquement libérée. Ce mécanisme s'appelle le drop.
Les trois règles
L'ownership repose sur trois règles fondamentales :
- Chaque valeur a exactement un propriétaire.
- Il ne peut y avoir qu'un seul propriétaire à la fois.
- Quand le propriétaire sort de la portée, la valeur est libérée (drop).
{
let s = String::from("bonjour"); // s est propriétaire
println!("{s}");
} // s sort de la portée → la mémoire est libéréeLa sémantique de move
Quand on assigne une valeur allouée sur le tas (comme un String) à une autre variable, l'ownership est transféré. C'est ce qu'on appelle un move.
let s1 = String::from("bonjour");
let s2 = s1; // s1 est "déplacé" vers s2
// println!("{s1}"); // Erreur ! s1 n'est plus valide
println!("s2 = {s2}"); // OKAprès le move, s1 n'est plus utilisable. Cela empêche les double free : deux variables qui tentent de libérer la même mémoire.
Si vous avez besoin d'une copie indépendante, utilisez la méthode clone() :
let s1 = String::from("bonjour");
let s2 = s1.clone(); // Copie profonde
println!("s1 = {s1}, s2 = {s2}"); // Les deux sont validesLe trait Copy
Les types simples stockés sur la pile (stack) — comme les entiers, flottants, booléens et caractères — implémentent le trait Copy. Pour ces types, l'assignation copie la valeur au lieu de la déplacer.
let x = 5;
let y = x; // Copie, pas un move
println!("x = {x}, y = {y}"); // Les deux sont valides !Retenez : les types qui implémentent Copy ne sont pas affectés par le move. Les types alloués sur le tas (comme String et Vec) ne sont pas Copy.
Ownership et fonctions
Passer une valeur à une fonction transfère l'ownership, exactement comme une assignation :
fn calculer_longueur(s: String) -> usize {
s.len()
} // s est libéré ici
fn main() {
let mot = String::from("salut");
let longueur = calculer_longueur(mot);
// mot n'est plus utilisable ici !
println!("La longueur est {longueur}");
}C'est contraignant : on perd l'accès à la variable après l'appel. La solution ? Les références, que nous verrons dans la prochaine leçon.
fn calculer_longueur(s: String) -> (String, usize) {
let longueur = s.len();
(s, longueur) // On retourne s pour rendre l'ownership
}
fn main() {
let mot = String::from("salut");
let (mot, longueur) = calculer_longueur(mot);
println!("La longueur de \"{mot}\" est {longueur}");
println!("{mot} est toujours accessible !");
}À vous de jouer
Essayez les commandes ci-dessous pour voir l'ownership en action :