Si vous débarquez d’un langage à ramasse-miettes (Java, Python, Go…), Rust vous accueille avec une porte fermée : le borrow checker. Il refuse de compiler du code qui semble parfaitement correct. La bonne nouvelle : derrière ses messages intimidants se cache une seule règle, et une fois qu’on la tient, l’essentiel de ses refus devient prévisible.
Emprunter au lieu de posséder
En Rust, chaque valeur a un propriétaire unique. Mais on n’a pas toujours besoin de posséder une donnée pour la lire ou la modifier — il suffit de l’emprunter, avec une référence :
fn longueur(s: &String) -> usize { // & : on emprunte, on ne possède pas
s.len()
}
let nom = String::from("Ada");
let n = longueur(&nom); // on prête `nom` le temps de l'appel
println!("{nom} fait {n} lettres"); // `nom` est toujours à nousDeux sortes d’emprunts existent : &T (partagé, lecture seule) et &mut T
(exclusif, lecture-écriture). Et c’est là qu’intervient la règle.
La règle unique : aliasing XOR mutation
À tout instant, pour une donnée : soit un seul
&mut(et rien d’autre), soit autant de&que vous voulez — jamais les deux en même temps.
C’est tout. Le borrow checker ne fait que vérifier ça. Ce code échoue :
let mut v = vec![1, 2, 3];
let premier = &v[0]; // un emprunt partagé est vivant…
v.push(4); // …et on tente de muter en même temps → refusé
println!("{premier}");Pourquoi ? Parce que push peut réallouer le vecteur en mémoire, rendant
premier pendouillant. Le compilateur ne contraint pas le langage : il
retourne la définition d’une course de données en interdiction. Ce qui était
un bug subtil à l’exécution devient une erreur à la compilation.
Comment satisfaire le checker
La plupart des refus se règlent en resserrant la durée de vie d’un emprunt : utilisez la référence, puis laissez-la mourir avant de muter.
let mut v = vec![1, 2, 3];
let premier = v[0]; // on copie la valeur au lieu d'emprunter
v.push(4); // plus aucun emprunt vivant → OK
println!("{premier}");Une fois cette règle intégrée, vous arrêtez de vous battre contre le compilateur : vous l’entendez énoncer un théorème sur votre code.
Coming from a garbage-collected language (Java, Python, Go…), Rust greets you with a closed door: the borrow checker. It refuses to compile code that looks perfectly fine. The good news: behind its intimidating messages hides a single rule, and once you hold it, most of its refusals become predictable.
Borrow instead of own
In Rust, every value has a single owner. But you don’t always need to own data to read or change it — you can just borrow it, with a reference:
fn length(s: &String) -> usize { // & : we borrow, we don't own
s.len()
}
let name = String::from("Ada");
let n = length(&name); // we lend `name` for the call
println!("{name} is {n} letters"); // `name` is still oursThere are two kinds of borrows: &T (shared, read-only) and &mut T
(exclusive, read-write). And that’s where the rule comes in.
The single rule: aliasing XOR mutation
At any moment, for one piece of data: either one
&mut(and nothing else), or as many&as you want — never both at once.
That’s it. The borrow checker only ever verifies this. This code fails:
let mut v = vec![1, 2, 3];
let first = &v[0]; // a shared borrow is alive…
v.push(4); // …and we try to mutate at the same time → rejected
println!("{first}");Why? Because push may reallocate the vector in memory, leaving first
dangling. The compiler doesn’t constrain the language: it flips the
definition of a data race into a prohibition. What used to be a subtle runtime
bug becomes a compile-time error.
How to satisfy the checker
Most refusals are fixed by tightening a borrow’s lifetime: use the reference, then let it die before you mutate.
let mut v = vec![1, 2, 3];
let first = v[0]; // copy the value instead of borrowing
v.push(4); // no live borrow left → OK
println!("{first}");Once this rule clicks, you stop fighting the compiler: you hear it state a theorem about your code.