Forked from
orestis.malaspin / rust-101
2 commits behind the upstream repository.
-
orestis.malaspin authoredorestis.malaspin authored
- Ownership
- Généralités
- Pile et tas (Stack and heap)
- La pile
- Le tas
- La propriété (Ownership)
- Les règles de la propriété
- Allocation de la mémoire: Manuelle
- Allocation de la mémoire: Garbage collection
- Allocation de la mémoire: Rust
- Allocation de mémoire (tas)
- Allocation de mémoire (Vec)
- Extension de la propriété
- Move
- Changement de propriétaire
- Changement de propriétaire: invalide
- Changement de propriétaire (3/3)
- Exception au move: Copy
- Différence entre Copie et Move
- Quand interviennent les move?
- Lors du passage en paramètre à une fonction
- Lors du retour d'une fonction
- Un mélange des deux
- L'emprunt (Borrowing)
- La référence
- La référence (schéma)
- Exemple 1
- Exemple 2
- Référence mutable
- Règles pour les références
- Sans la règle sur la référence mutable
- Dangling pointer ("pointeur pendouillant")
- Exemples (1/2)
- Exemples (2/2)
- Déréférencement
ownership.md 10.46 KiB
Ownership
Généralités
- Les règles sur la
propriété
sont ce qui rend Rust unique. - La
propriété
est un ensemble de règles vérifiées à la compilation. - Garantit la bonne gestion de la mémoire sans
garbage collector
. - Oblige à réfléchir à ce que fait notre programme quand on le compile (et que le compilateur râle).
- Spoiler alert: simple à expliquer, difficile à appliquer.
Pile et tas (Stack and heap)
La pile
- Partie de la mémoire réservée pour chaque thread.
- Lorsqu'une fonction est appelée, un bloc est réservé pour stocker les variables locales.
- Lorsque la fonction retourne, la mémoire est libérée et peut être réutilisée.
- C'est une structure LIFO (Last in first out): le dernier ajout sera libéré en premier.
- Stocke typiquement les objets dont la taille est connue à la compilation (entiers, nombres à virgule flottante, caractères, booléens, ...).
Le tas
- Partie de la mémoire réservée est commune à tous les threads.
- Utilisée pour l'allocation dynamique (la taille des objets peut varier en cours d'exécution).
- Peut modifier de la mémoire non-locale à une fonction.
- Pas de structure particulière pour l'allocation/désallocation.
- Plus compliqué de gérer l'allocation/désallocation.
- Typiquement plus lent que la pile (il faut chercher de la place pour l'allocation et "sauter" en mémoire pour la retrouver).
La propriété (Ownership)
Les règles de la propriété
- Chaque valeur a une variable qui est son propriétaire (
owner
). - Une valeur ne peut avoir qu'un seul propriétaire à chaque instant.
- Quand le programme sort de la portée du propriétaire, la valeur est détruite (
dropped
).
fn main() {
let x = 5; // x est propriétaire de la mémoire contenant 5
{
let y = 6; // y est propriétaire de la mémoire contenant 6
println!("La valeur de (x,y) est: ({}, {}).", x, y);
} // y sort de la portée et est détruite avec la valeur 6
println!("La valeur de x est: {}", x);
} // x sort de la portée et sa valeur est détruite
Allocation de la mémoire: Manuelle
- Manuellement (C/C++, ...): on ordonne à l'OS d'allouer/désallouer de la mémoire sur le tas.
- Oublier de désallouer la mémoire allouée (fuite mémoire/memory leak).
- Désallouer de la mémoire trop tôt (dangling pointer et comportement indéfini).
- Libérer la mémoire à double.
Allocation de la mémoire: Garbage collection
- Automatiquement: on a un "garbage collector" (java, scala, ...).
- Consomme des ressources.
- Est une "boîte magique": il fait ce qu'il veut.
Allocation de la mémoire: Rust
- En Rust on contrôle où et quand on alloue/désalloue à la compilation:
- Difficulté: il faut suivre des règles très strictes.
- Garbage collection "à la compilation".
Allocation de mémoire (tas)
- Les types vus jusque là sont stockés dans la pile: leur taille est connue à la compilation.
- Que se passe-t-il lorsque la taille est inconnue à la compilation?
fn main() {
let x = [1, 2, 3, 4]; // x de type [i32; 4], sur la pile
// on ne peut pas augmenter sa taille
println!("La valeur de x est: {:?}", x);
let mut y = Vec::new(); // un vecteur dont la taille est variable
for i in 0..5 { // On rajoute des éléments au vecteur avec push(elem)
y.push(i);
}
println!("La valeur de y est: {:?}", y);
} // x/y sortent de la portée, il sont détruits et la mémoire est libérée
Allocation de mémoire (Vec)
- Pile: 1 pointeur vers le tas, et 2 entiers (longueur et capacité).
- Tas: 1, 2, 3, 4.
- Variable (proprétaire) sort de la portée, mémoire automatiquement libérée.
Extension de la propriété
Flexibiliser la propriété:
- Donner la propriété à un autre propriétaire:
move
. - Emprunter pour un temps les données:
borrow
. - Être co-propriétaires dans des structures avancées:
Rc
etArc
.
Move
Changement de propriétaire
fn main() {
let mut y = Vec::new();
for i in 0..5 {
y.push(i);
}
println!("La valeur de y est: {:?}", y);
let z = y; // le vecteur (1,2,3,4) est maintenant propriété de z
// y est une variable non initialisée
println!("La valeur de z est: {:?}", z);
} // z sort de la portée, il est détruit et la mémoire est libérée
Changement de propriétaire: invalide
fn main() {
let mut y = Vec::new();
for i in 0..5 {
y.push(i);
}
println!("La valeur de y est: {:?}", y);
let z = y; // le vecteur (1,2,3,4) est maintenant propriété de z
// y est une variable non initialisée
println!("La valeur de y est: {:?}", y);
} // Ce code ne compilera pas.
Changement de propriétaire (3/3)
- La variable
y
et copiée dans la variablez
. - En ne faisant rien on a deux propriétaires des données.
- Illégal: on invalide
y
.
move
: Copy
Exception au fn main() {
let y = 1;
let mut z = y; // Généralement: move
// y est i32: on copie puis
// on assigne la valeur à z
println!("Les valeurs de y et z sont : ({}, {})", y, z);
z = 2; // comme la valeur est copiée modifier z ne modifie pas y
println!("Les valeurs de y et z sont : ({}, {})", y, z);
}
Différence entre Copie et Move
- move: copie uniquement la variable et le propriétaire change.
-
copie: on duplique la variable et les données.
move
?
Quand interviennent les - Si le type de la variable n'est pas
Copy
:- Lors d'une assignation.
- Lors du passage en paramètre à une fonction.
- Lors du retour d'une fonction.
Lors du passage en paramètre à une fonction
fn take_own(_v: Vec<i32>) {
// on fait des choses
}
fn main() {
let mut y = Vec::new(); // un vecteur dont la taille est variable
// On rajoute des éléments au vecteur avec push(elem)
y.push(1); y.push(2); y.push(3); y.push(4);
take_own(y);
println!("La valeur de y est: {:?}", y);
} // A votre avis que se passe-t-il?
Lors du retour d'une fonction
fn give_own() -> Vec<i32> {
let mut y = Vec::new(); // un vecteur dont la taille est variable
y.push(1); y.push(2); y.push(3); y.push(4);
y // on retourne y
}
fn main() {
let y = give_own();
println!("La valeur de y est: {:?}", y);
} // A votre avis que se passe-t-il?
Un mélange des deux
fn get_len(v: Vec<i32>) -> (Vec<i32>, usize) {
let length = v.len(); // on ajoute 2 au vecteur
(v, length) // on retourne v et sa longueur
}
fn main() {
let mut y = Vec::new();
y.push(1); y.push(2); y.push(3); y.push(4);
let (y, length) = get_len(y);
println!("La valeur de y est: {:?} et sa longueur {}", y, length);
} // A votre avis que se passe-t-il?
L'emprunt (Borrowing)
La référence
- Le
move
est trop contraignant, la copie lente (et pas toujours ce qu'on veut). - Il est pratique de pouvoir emprunter les objets.
- Le borrowing permet d'accéder aux données sans avoir la propriété de l'objet.
- Cela se fait à l'aide d'une référence sur l'objet qu'on souhaite emprunter.
- Si
y
est une variable,&y
est la référence vers la variable (le pointeur vers cette variable). - La référence permet l'emprunt de données sans en prendre la propriété.
La référence (schéma)
Exemple 1
fn get_len(v: &Vec<i32>) -> usize {
v.len()
}
// on sort de la portée de la fonction,
// la propriété des données dans v est rendue.
fn main() {
let mut y = Vec::new();
y.push(1); y.push(2); y.push(3); y.push(4);
let length = get_len(&y); // la référence vers y est passée
println!("La valeur de y est: {:?} et sa longueur {}", y, length);
}
Exemple 2
fn get_len(v: &Vec<i32>) -> usize {
v.push(2); // on ajoute 2 à v
v.len()
}
// on sort de la portée de la fonction,
// la propriété des données dans v est rendue.
fn main() {
let mut y = Vec::new();
y.push(1); y.push(2); y.push(3); y.push(4);
let length = get_len(&y); // la référence vers y est passée
println!("La valeur de y est: {:?} et sa longueur {}", y, length);
}
Référence mutable
fn get_len(v: &mut Vec<i32>) -> usize { // référence mutable
v.push(2);
v.len()
}
fn main() {
let mut y = Vec::new(); // variable mutable
y.push(1); y.push(2); y.push(3); y.push(4);
let length = get_len(&mut y); // référence mutable en argument
println!("La valeur de y est: {:?} et sa longueur {}", y, length);
}
Règles pour les références
- Sur une variable on peut avoir:
- Autant de références immutables qu'on veut.
- Une seule référence mutable.
- La référence doit toujours être valide.
Sans la règle sur la référence mutable
fn main() {
let mut y = Vec::new();
let z = &y;
y.push(1); y.push(2); y.push(3); y.push(4);
println!("La valeur de y est: {:?}
et celle de z est {:?}", y, z); // Quel est le problème?
}
Dangling pointer ("pointeur pendouillant")
fn dangling() -> &Vec<i32> { // la fonction retourne une référence vers un pointeur
let mut v = Vec::new();
v.push(1); v.push(2); // on a créé un vec avec 1,2 dedans.
&v; // on retourne une réf vers v
} // v sort de la portée de la fonction et est détruit:
// la mémoire est libérée.
fn main() {
let dangling_reference = dangling();
}
- Mémoire désallouée et le pointeur vers cette mémoire: dangling pointer.
- Tout un tas de langages autorise ce comportement indéfini.
Exemples (1/2)
fn main() {
let mut y = Vec::new();
y.push(1); y.push(2); y.push(3); y.push(4);
let y1 = &y;
let y2 = &y;
println!("La valeur de y1 et y2 sont: {:?}, {:?}.", y1, y2);
}
Exemples (2/2)
fn main() {
let mut y = Vec::new();
y.push(1); y.push(2); y.push(3); y.push(4);
{ // optionnel, le compilo est smart
let mut y1 = &mut y;
y1.push(7);
}
println!("La valeur de y est: {:?}.", y);
}
Déréférencement
fn main() {
let mut y = Vec::new();
y.push(1); y.push(2); y.push(3); y.push(4);
let z = &y;
println!("La valeur de z est: {:?}.", z);
println!("La valeur de z est: {:?}.", *z);
}