2. Ownership, borrowing et gestion mémoire sans garbage collector

L’ownership (propriété) est le mécanisme central qui rend Rust unique. C’est lui qui permet au compilateur de garantir la sécurité mémoire sans garbage collector. Dans cette unité, vous allez comprendre les trois règles fondamentales de l’ownership, le système de borrowing (emprunt), et pourquoi le borrow checker — parfois frustrant au début — est votre meilleur allié pour écrire du code fiable.

 

Le problème que Rust résout

En C, il est facile d’écrire du code qui libère une zone mémoire deux fois (double free), qui accède à de la mémoire après l’avoir libérée (use-after-free), ou qui a des data races en contexte multi-thread. Ces bugs sont à l’origine de la majorité des vulnérabilités de sécurité dans les logiciels système. Les langages avec GC (Java, Python) évitent ces bugs au prix de la performance et de la prévisibilité. Rust refuse le compromis : sécurité ET performance, garanties à la compilation.

 

Les trois règles de l’ownership

Tout le système repose sur trois règles simples :

  • Chaque valeur en Rust a un propriétaire (owner)
  • Il ne peut y avoir qu’un seul propriétaire à la fois
  • Quand le propriétaire sort de portée, la valeur est libérée automatiquement
fn main() {
    let s1 = String::from("bonjour");  // s1 est propriétaire
    let s2 = s1;                       // s1 est MOVED vers s2
    // println!("{}", s1);             // ERREUR : s1 n'existe plus !
    println!("{}", s2);                // OK
} // s2 sort de portée → mémoire libérée automatiquement

Move vs Copy

Les types simples qui tiennent sur la stack (i32, f64, bool, char…) implémentent le trait Copy : ils sont dupliqués automatiquement lors d’une assignation. Les types heap (String, Vec, Box…) font un move : le propriétaire change.

let x = 5;
let y = x;  // Copy : x et y sont deux valeurs indépendantes
println!("{} et {}", x, y);  // OK !

 

Le borrowing : emprunter sans posséder

Pour passer une valeur à une fonction sans en transférer la propriété, on utilise des références avec l’opérateur &. C’est ce qu’on appelle le borrowing.

fn longueur(s: &String) -> usize {
    s.len()  // on emprunte s, on ne le possède pas
}

fn main() {
    let s = String::from("Culte du Code");
    let len = longueur(&s);  // on passe une référence
    println!("{} fait {} caractères", s, len);  // s toujours valide !
}

Références mutables

Pour modifier une valeur empruntée, il faut une référence mutable (&mut). Le borrow checker impose une règle stricte : une seule référence mutable à la fois, et elle ne peut pas coexister avec des références immuables.

fn ajouter_texte(s: &mut String) {
    s.push_str(", bienvenue !");
}

fn main() {
    let mut s = String::from("Rust");
    ajouter_texte(&mut s);
    println!("{}", s);  // "Rust, bienvenue !"
}

 

Le borrow checker en action

Le compilateur vérifie ces règles statiquement. Au début, les messages d’erreur peuvent sembler cryptiques, mais ils sont en réalité très précis et vous indiquent exactement où se situe le problème :

let mut s = String::from("hello");
let r1 = &s;      // référence immuable
let r2 = &s;      // autre référence immuable — OK
// let r3 = &mut s; // ERREUR : ne peut pas emprunter s mutuellement
// alors que r1 et r2 existent encore
Points clés :

  • Chaque valeur a un seul propriétaire ; quand il sort de portée, la mémoire est libérée
  • L’opérateur & crée une référence (emprunt) sans transférer la propriété
  • Plusieurs &T peuvent coexister, mais un seul &mut T à la fois
  • Le borrow checker est votre filet de sécurité : ses erreurs vous protègent
SEE ALL Add a note
YOU
Add your Comment