Phase 2Ownership & emprunt

#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 :

  1. Chaque valeur a exactement un propriétaire.
  2. Il ne peut y avoir qu'un seul propriétaire à la fois.
  3. Quand le propriétaire sort de la portée, la valeur est libérée (drop).
Portée et ownership
{
    let s = String::from("bonjour"); // s est propriétaire
    println!("{s}");
} // s sort de la portée → la mémoire est libérée

La 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.

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}"); // OK

Aprè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() :

Clone
let s1 = String::from("bonjour");
let s2 = s1.clone(); // Copie profonde

println!("s1 = {s1}, s2 = {s2}"); // Les deux sont valides

Le 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.

Copy avec les entiers
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 :

Ownership et fonctions
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.

Retourner l'ownership
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 :

terminal — cargo
user@stemlegacy:~/ownership-demo$