Phase 2Ownership & emprunt

#6 Références et emprunt

&, &mut, règles d'emprunt

Qu'est-ce que l'emprunt ?

Dans la leçon précédente, nous avons vu que passer une valeur à une fonction transfère l'ownership. L'emprunt (borrowing) permet d'utiliser une valeur sans en prendre la propriété, grâce aux références.

Une référence est comme un pointeur qui renvoie vers les données du propriétaire. Le propriétaire garde le contrôle de la mémoire.

Références immuables (&T)

Une référence immuable permet de lire une valeur sans la modifier ni en prendre possession. On la crée avec &.

Référence immuable
fn calculer_longueur(s: &String) -> usize {
    s.len()
} // s est une référence → rien n'est libéré

fn main() {
    let mot = String::from("bonjour");
    let longueur = calculer_longueur(&mot); // On passe une référence
    println!("La longueur de \"{mot}\" est {longueur}");
    println!("{mot} est toujours accessible !");
}

Notez le & à deux endroits : dans le type du paramètre (&String) et lors de l'appel ( &mot). La variable mot reste valide après l'appel car l'ownership n'a pas été transféré.

On peut avoir autant de références immuables qu'on veut en même temps :

Plusieurs références immuables
let s = String::from("bonjour");

let r1 = &s;
let r2 = &s;
let r3 = &s;

println!("{r1}, {r2}, {r3}"); // Tout va bien

Références mutables (&mut T)

Pour modifier une valeur empruntée, il faut une référence mutable avec &mut. La variable d'origine doit aussi être déclarée mut.

Référence mutable
fn ajouter_texte(s: &mut String) {
    s.push_str(", monde !");
}

fn main() {
    let mut mot = String::from("bonjour");
    println!("Avant : {mot}");
    ajouter_texte(&mut mot);
    println!("Après : {mot}");
}

Trois éléments doivent correspondre : la variable est mut, le paramètre est &mut String, et l'appel utilise &mut mot.

Règles d'emprunt

Le compilateur Rust applique des règles strictes pour éviter les accès concurrents dangereux. Voici les deux règles fondamentales :

  1. Plusieurs références immuables (&T) sont autorisées en même temps.
  2. Une seule référence mutable (&mut T) est autorisée à la fois, et aucune référence immuable ne peut coexister.
Conflit interdit
let mut s = String::from("bonjour");

let r1 = &s;     // OK : référence immuable
let r2 = &s;     // OK : deuxième référence immuable
// let r3 = &mut s; // Erreur ! On ne peut pas emprunter
                     // en mutable alors que des références
                     // immuables existent

println!("{r1}, {r2}");
Référence mutable unique
let mut s = String::from("bonjour");

let r1 = &mut s; // OK : une seule référence mutable
// let r2 = &mut s; // Erreur ! Pas deux &mut en même temps

r1.push_str(" !");
println!("{r1}");

Ces règles sont vérifiées à la compilation, pas à l'exécution. Elles empêchent les data races et les accès mémoire invalides sans aucun coût à l'exécution.

À vous de jouer

Essayez les commandes ci-dessous pour voir les références en action :

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