Structures en Rust
Premières structures en Rust
1. Introduction
Le type « structure » en Rust permet de réunir différents types (comme on le ferait avec un tuple). D’autre part, la structure peut être munie de méthodes. Enfin, et c’est là son intérêt, on peut instancier une structure.
On reconnaît ici quelque chose qui ressemble à une classe dans nombre de langages orientés objet : on a à faire plus ou moins à la structure C/C++ ou encore à quelque chose de similaire à la classe Python. En l’occurrence, Rust est un langage multiparadigme parmi lesquels on trouve partiellement l’approche objet, en particulier grâce aux structures.
Avec les structures (et les énumérations également, qui seront étudiées dans le chapitre suivant), on peut définir de nouveaux types. Et ces nouveaux types ont une utilisation de la mémoire qui va être vérifiée a priori par la compilation. Ainsi, les types natifs ou les nouveaux types sont vérifiés de la même façon par le compilateur Rust. En quelque sorte, le présent chapitre pourrait s’intituler : « création de nouveaux types en Rust, sécurisés au niveau de leurs utilisations en mémoire, dès la compilation ».
Plusieurs types de structures existent en langage Rust. Procédons à un inventaire.
2. Structure à champs nommés
La structure Rust se définit grâce au mot-clé struct. Entrons d’emblée dans le vif du sujet, avec les structures dites à champs nommés.
a. Syntaxe
Dans ce type de structure, chaque champ a un nom, qui nous permettra ensuite d’accéder aux valeurs respectives de chacun d’eux.
On nomme notre structure et, à l’intérieur, on définit chaque champ ainsi, en précisant son nom et son type :
NOM_DU_CHAMP : TYPE_DU_CHAMP,
On crée ainsi une structure Cercle dans laquelle on trouve les champs coordonnées en abscisse et en ordonnée du centre, le champ rayon et le champ nom (on donne un nom à chacun de nos cercles) :
struct Cercle{
coord_centre_x : i64,
coord_centre_y : i64,
rayon_cercle...
Les méthodes de structure en Rust
Outre les champs, nommés ou non, la structure Rust peut disposer de fonctions de structures, que l’on appelle méthodes.
Pour ce faire, on utilise le mot-clé impl. Autre différence avec C++ : on définit les méthodes de la structure, en dehors de la définition de ladite structure.
1. Prototype des méthodes
Pour définir une méthode de structure, on se place à l’extérieur dans ce que l’on appelle un bloc d’implémentation. Il commence par le mot-clé impl et doit avoir le même nom que la structure. On respecte la syntaxe suivante :
impl [NOM DE LA STRUCTURE] {
pub fn [NOM DE LA METHODE 1]() -> [TYPE DE RETOUR] {
}
pub fn [NOM DE LA METHODE 2]() -> [TYPE DE RETOUR] {
}
}
Dans l’exemple du Rectangle, on aura donc quelque chose comme cela :
impl Rectangle{
pub fn perimetre(&self) -> f64 {
On voit d’ailleurs que l’on passe en paramètre &self (il pourrait y avoir d’autres paramètres). Voyons à quoi cela correspond.
2. Usage du mot-clé self
Comme en C++ ou en Python, et dans la plupart des langages orientés objet, il est possible d’utiliser une référence sur l’instance courante.
En C++, c’est le mot-clé this. En Rust (comme en Python), c’est le mot-clé self.
Le langage Rust autorise d’écrire de plusieurs manières cette référence vers l’instance courante, lorsqu’on la passe en paramètre ; ceci est obligatoire, comme en langage Python, pour pouvoir l’utiliser dans le corps de la méthode. Pour rappel, c’est différent en langage C++ où this est disponible de toute façon.
Les trois manières d’écrire...
Structure générique
1. Introduction
Nous allons encore un peu loin en étudiant à présent les structures génériques. Si vous avez fait du C++, vous êtes familier des fonctions templates dans lesquelles on peut manipuler un type générique. C’est un peu la même chose qui est réalisée ici avec les structures génériques. En effet, une structure générique s’applique à un type générique et concrètement le même code va pouvoir s’appliquer à un type entier, flottant, chaîne de caractères, etc. (selon le contexte fonctionnel bien sûr).
2. Exemple support
On se propose de créer une structure qui encapsule une structure de données de type vecteur qui stocke des entiers de type i64. On va munir cette structure d’une méthode qui retourne la longueur, d’une autre qui retourne la valeur d’un indice donné et d’une troisième qui permet d’ajouter un nouvel élément au vecteur.
Pour ce faire, on commence par créer un nouveau projet Rust :
cargo new structs_generiques -bin
On va directement utiliser une structure de données de vecteurs Vec disponible dans la librairie standard de Rust. Voici sa page de documentation : https://doc.rust-lang.org/std/vec/struct.Vec.html
On définit ainsi notre structure :
pub struct VecteurEntiers{
vec : Vec<i64>
}
Les implémentations des méthodes de la structure sont les suivantes :
impl VecteurEntiers{
pub fn creer() -> VecteurEntiers{
VecteurEntiers...
La question des références dans une structure
1. Introduction
Une structure peut parfaitement inclure en son sein des champs qui s’avèrent être une référence.
Si on résume les principes qui régissent les références en Rust, telles qu’étudiées dans les chapitres précédents, on peut établir les trois règles suivantes :
-
Règle 1 : une référence ne peut pas « vivre » plus longtemps que l’objet qu’elle pointe. Autrement dit, si l’objet pointé meurt, il faut que la référence qui pointait sur lui meure absolument avant.
-
Règle 2 : une référence en &mut est dite exclusive, c’est-à-dire qu’aucune autre référence ne pourra pointer vers l’objet pointé par la référence exclusive. Autrement dit, l’objet pointé a une et une seule référence pointant vers lui.
-
Règle 2.1 : cette règle est une conséquence de la règle 2. S’il existe au moins une référence & vers un objet, il ne sera pas possible d’y ajouter une référence exclusive &mut (justement car elle est exclusive).
Une conséquence importante est que la durée de vie d’une référence doit nécessairement être moindre que celle de l’objet pointé. Plus exactement, la mort de la référence doit être strictement antérieure à celle de l’objet pointé....
Notion de traits appliqués aux structures
1. Introduction
La notion de trait pourrait se définir ainsi : c’est une sorte de collections de méthodes qui s’applique à self et donc à un type a priori inconnu. Exprimé ainsi, cela semble assez flou ; si vous avez fait du C# ou du JavaScript, ce n’est ni plus ni moins que la notion d’interface. Même s’il y a quelques nuances, cela peut évoquer la notion de classe abstraite en C++.
La notion fait l’objet d’un chapitre ultérieur : en effet, il est possible en Rust de définir ses propres traits, ce qui constitue un autre des nombreux avantages du langage.
Nous évoquons la notion ici, car c’est l’occasion de l’introduire d’une part, et pour aborder les traits prédéfinis dans le langage, appliqués aux structures.
2. Les traits prédéfinis appliqués aux structures
Nous n’aborderons ici que les traits prédéfinis, c’est-à-dire les traits dont l’implémentation est déjà fournie : le compilateur se débrouille pour associer une implémentation par défaut pour les traits attendus.
On précise les traits attendus ainsi. Ici, on voudrait avoir le trait Clone qui permet de cloner une instance :
#[derive(Clone)]
Les traits prédéfinis sont...
Conclusion
Ce chapitre nous a permis d’approfondir le sujet de la structure en Rust, tout en abordant certains sujets connexes comme les visibilités et la notion de trait. Passons à présent à un autre « gros morceau » du langage : les énumérations.