Un cyber Pearl Harbor est-il possible ? (25/06/2024 à 18h) : inscrivez-vous !

Qu’est-ce que l’ownership en Rust ?

2024-06-10
Ownership en rust

L’ownership en Rust est un des concepts fondamentaux du langage qui garantit la sécurité de la mémoire et prévient les problèmes de concurrence. En comprenant les règles de l’ownership et en utilisant les références lorsque nécessaire, Rust nous permet d’écrire un code sûr et efficace. Bien que cela puisse sembler complexe au début, une fois que l’on maîtrise les concepts de base, on peut se détendre et apprécier coder en Rust 🦀

La gestion de la mémoire

Avant de parler de Rust, il me semble important de revenir sur le concept de gestion de la mémoire dans un ordinateur. Venant du développement web, je n’avais pas vraiment de notions sur la gestion de la mémoire, ou du moins c’était assez enfoui. Logique, vu que la majorité des langages web ont été conçus pour gérer la mémoire à la place du développeur. Lorsque je me suis lancé dans Rust, j’ai dû revenir sur des concepts de computer science, dont le fonctionnement de la mémoire. Pour m’aider, comme toujours, le Book Rust ! En effet, dans à peu près tous les chapitres, il y a des explications complémentaires laissées pour ceux qui auraient quelques lacunes en computer science. Prenez donc le temps de lire ce contenu sur la gestion de la mémoire avant de passer à la suite 😌.

Si vous connaissez déjà le fonctionnement de la mémoire, notamment parce que vous venez de langages tels que C / C++, et bien que cela nécessite de comprendre un nouveau concept, vous devriez apprécier de voir que Rust peut vous permettre de vous guider dans la gestion de la mémoire. Et si vous me dites que vous perdez en liberté, sachez que le concept de pointeur existe aussi dans Rust, et que vous pouvez aussi faire taire complètement les alertes de Rust à la compilation grâce à unsafe (pas recommandé évidemment 😄).

C’est quoi l’ownership en Rust ?

En Rust, chaque variable est propriétaire d’une valeur. Il ne peut y avoir qu’un seul propriétaire à la fois pour cette valeur. Lorsque le propriétaire sort de la portée, la valeur est libérée.

Commençons par un exemple très simple :

				
					fn main() {
	// On déclare une variable qui détient la chaîne de caractères "hello"
    let message = String::from("hello");
    println!("{}", s);
} // la variable 'message' sort de la portée et est libérée ici

				
			

Dans cet exemple, message est une chaîne (String) nouvellement créée. Lorsque message sort de la portée à la fin de la fonction main(), la mémoire allouée pour la chaîne est libérée automatiquement.

Voici un exemple un peu plus complexe :

				
					fn main() {
    let message = String::from("hello");
    let len = calculate_length(message); // ici, la fonction prend possession de `message`
    println!("La longueur de '{}' est {}.", message, len);
    // On obtient une erreur car `message` a été libéré auparavant
}

fn calculate_length(s: String) -> usize {
    s.len()
} 

// error[E0382]: borrow of moved value: `message`
//  --> src/main.rs:4:45
//   |
// 2 |     let message = String::from("hello");
//   |         ------- move occurs because `message` has type `String`, which does not implement the `Copy` trait
// 3 |     let len = calculate_length(message); // ici, `len` prend possession de `message`
//   |                                ------- value moved here
// 4 |     println!("La longueur de '{}' est {}.", message, len);
//   |                                             ^^^^^^^ value borrowed here after move
				
			

Dans cet exemple, on souhaite calculer la longueur d’une chaîne de caractères à l’aide de la fonction calculate_length. Pour cela on déclare une variable message puis on passe cette variable dans la fonction calculate_length. Lorsque la variable passe dans la fonction, elle est libérée et n’est donc plus utilisable dans le programme. C’est pour cela que l’on obtient une erreur lorsqu’on essaye de faire appel à la variable message juste après.

En Rust, c’est ce qu’on appelle le transfert de propriété (ownership transfer). Le transfert de propriété est le mécanisme de base pour gérer les données. Lorsqu’une valeur est assignée à une autre variable, elle est transférée de la variable source à la variable cible. Cela signifie que la variable source n’a plus accès à la valeur et la variable cible devient le nouveau propriétaire.

Dans l’exemple ci-dessus toujours, à la fin, je vous ai collé le retour d’erreur quand on essaye de compiler le programme. Ici, l’erreur nous indique simplement qu’on essaye d’emprunter une variable qui a “bougé” (moved). En effet, si vous avez bien compris, la variable message a effectivement “bougé” lorsqu’elle est “partie” dans la fonction calculate_length. Cela confirme une nouvelle fois que la variable n’est plus utilisable en l’état.

				
					fn main() {
    let x = 5;
    let y = x; // x est copié, pas transféré
    println!("x: {}, y: {}", x, y); // x et y sont tous les deux valides
}
				
			

Dans cet exemple, x est copié dans y, donc les deux variables restent valides.

“Emprunter” une donnée : les références

Pour partager des données sans transfert de propriété, Rust utilise des références. Une référence permet à une variable de référencer la valeur d’une autre variable sans en devenir le propriétaire.

Reprenons l’exemple précédent qui produisait une erreur, et corrigeons-le :

				
					fn main() {
    let message = String::from("hello");
    let len = calculate_length(&message); // ici avec le symbole "&" on indique le souhait d'emprunter la valeur
    println!("La longueur de '{}' est {}.", message, len);
}

fn calculate_length(s: &String) -> usize {
    s.len() // pas de transfert de propriété, s est une référence à la chaîne
}
				
			

Dans cette fonction, calculate_length prend une référence (&String) en argument, ce qui signifie qu’elle emprunte la chaîne plutôt que de la posséder. Ainsi, la chaîne reste valide même après l’appel de la fonction.

 

Donc cette histoire d’ownership en Rust, c’est juste une histoire de propriété ! On doit essayer de se souvenir de ce que possède chaque variable au fil de notre code, et puis si on se trompe, il y a toujours une remontée d’erreur qui permet de régler le problème ! J’espère que tout cela semble un peu moins insurmontable, et que cela va vous donner envie de coder en Rust 🦀 ! Pour aller plus loin, je vous ai aussi réalisé une sélection de ressources en français pour apprendre le Rust et surtout, il y a notre belle formation Rust, accessible si vous avez des bases de programmation orientée objet.