1. Ownership, Borrowing, Lifetimes
Le système d’ownership est ce qui rend Rust unique. Il garantit la sécurité mémoire sans garbage collector. Comprendre l’ownership est la clé de tout le reste.
1.1 Ownership — Règles Fondamentales
Chaque valeur en Rust a un propriétaire unique. Trois règles :
- Chaque valeur a exactement un propriétaire à la fois.
- Quand le propriétaire sort de portée, la valeur est libérée (
drop). - La propriété peut être transférée (move) ou empruntée (borrow).
fn main() {
let s1 = String::from("hello"); // s1 est propriétaire
let s2 = s1; // MOVE : s1 n'est plus valide
// println!("{}", s1); // ⛔ ERREUR : s1 a été moved
println!("{}", s2); // ✅ s2 est le nouveau propriétaire
}Move vs Copy
| Type | Comportement | Exemples |
|---|---|---|
| Copy | Dupliqué implicitement (pas de move) | scalaires : i32, f64, bool, char, tuples de Copy |
| Move | Transféré, l’original n’est plus valide | String, Vec, pointeurs, struct sans #[derive(Copy)] |
let x = 42; // i32 implémente Copy
let y = x; // copie, x reste valide
println!("{x} {y}"); // ✅
let s = String::from("hello"); // String n'est pas Copy
let t = s; // move !
// println!("{s}"); // ⛔Portée et libération
{
let s = String::from("hello");
// s est valide ici
} // ← s sort de portée, drop() libère la mémoire automatiquementFonctions et ownership
fn take_ownership(s: String) { // prend ownership
println!("{s}");
} // ← drop ici
fn give_ownership() -> String {
String::from("world") // donne ownership à l'appelant
}
let s = give_ownership();
take_ownership(s);
// println!("{s}"); // ⛔ moved dans take_ownership1.2 Borrowing — Emprunter sans posséder
Références immutables (&T)
On peut créer autant de références immutables qu’on veut. Elles ne permettent pas de modifier la valeur.
fn calculate_length(s: &String) -> usize { // emprunte (borrow)
s.len()
} // ← pas de drop : le propriétaire original garde la valeur
let s = String::from("hello");
let len = calculate_length(&s); // prête s à la fonction
println!("{s} a une longueur de {len}"); // ✅ s toujours valideRéférences mutables (&mut T)
Une seule référence mutable peut exister à la fois. Elle permet de modifier la valeur.
fn append_world(s: &mut String) {
s.push_str(", world");
}
let mut s = String::from("hello");
append_world(&mut s);
println!("{s}"); // "hello, world"Règles du borrowing
| Situation | Autorisé |
|---|---|
| 1 référence mutable OU N références immutables | ✅ |
| 1 référence mutable ET 1 référence immutable | ⛔ |
| 2 références mutables simultanées | ⛔ |
let mut s = String::from("hello");
let r1 = &s; // ✅
let r2 = &s; // ✅ (plusieurs immutables)
// let r3 = &mut s; // ⛔ (déjà emprunté immuablement)
println!("{r1} {r2}"); // les emprunts immuables se terminent ici
let r3 = &mut s; // ✅ (les précédents ne sont plus utilisés)
r3.push_str("!");Dangling references — le compilateur les empêche
fn dangle() -> &String { // ⛔ retourne une référence vers...
let s = String::from("hello");
&s // ...une valeur qui sera libérée ici
} // ← s libéré, la référence pointe vers rien
fn no_dangle() -> String { // ✅ solution : retourner la valeur
let s = String::from("hello");
s // transfert d'ownership
}1.3 Lifetimes — Garantir la validité des références
Les lifetimes sont des annotations qui indiquent au compilateur pendant combien de temps une référence est valide. Le compilateur les infère souvent tout seul.
Syntaxe
// 'a est un paramètre de lifetime : "les deux références doivent vivre au moins aussi longtemps que 'a"
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}Règles d’inférence (elision)
Dans la plupart des cas, le compilateur déduit les lifetimes tout seul :
fn first_word(s: &str) -> &str { // lifetime implicite
s.split_whitespace().next().unwrap_or("")
}
// Équivalent à :
fn first_word<'a>(s: &'a str) -> &'a str { ... }Lifetime dans les structs
struct Texte<'a> {
contenu: &'a str, // le struct ne peut pas vivre plus longtemps que contenu
}
fn main() {
let texte;
{
let data = String::from("hello");
texte = Texte { contenu: &data };
// ✅ data vit encore dans cette portée
}
// ⛔ data libérée, texte.contenu invalide
}Static lifetime
'static signifie que la référence vit pendant toute la durée du programme :
let s: &'static str = "hello"; // string literal, toujours valideRésumé des règles
| Concept | Règle |
|---|---|
| Une valeur | Un seul propriétaire à la fois |
| Drop | Automatique à la sortie de portée |
| Move | Transfère la propriété, l’original devient invalide |
Borrow &T | N références immutables simultanées |
Borrow &mut T | 1 seule référence mutable |
| Lifetime | La référence ne peut pas vivre plus longtemps que son emprunteur |
| Dangling | Empêché à la compilation — jamais de pointeur invalide |
Exercices (rustlings)
// 1. Corriger : déplacer sans copier
fn main() {
let s = String::from("hello");
let t = s; // s est moved
println!("{s}"); // que faut-il changer ?
}
// 2. Corriger : emprunt mutable multiple
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{r1} {r2}");
}
// 3. Lifetime : corriger
fn smallest<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {
if x < y { x } else { y }
}
fn main() {
let x = 10;
let result;
{
let y = 20;
result = smallest(&x, &y);
}
println!("{result}");
}