3. Les types personnalisés▲
En Rust, les types de données personnalisés sont principalement créés à partir de ces deux mots-clés :
struct
: Définit une structure ;enum
: Définit une énumération.
Les constantes peuvent également être créées via les mots-clés const
et static
.
3-1. Les structures▲
Il y a trois types de structures pouvant être créé en utilisant le mot-clé struct
:
- Les « tuple structs », aussi appelées simplement tuples ;
- Les structures classiques issues du langage C ;
- Les structures unitaires. Ne possèdant aucun champ, elles sont utiles pour la généricité.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
// Une structure unitaire.
struct
Nil;
// Un tuple.
struct
Pair(i32, f32);
// Une structure avec deux champs.
struct
Point {
x: f32,
y: f32,
}
// Les structures peuvent faire partie des champs d'une autre structure.
#[allow(dead_code)]
struct
Rectangle {
p1: Point,
p2: Point,
}
fn
main() {
// On instancie la structure `Point`.
let
point: Point = Point { x: 0
.3
, y: 0
.4
};
// On accède aux champs du point.
println!
("point coordinates: (
{}
,
{}
)"
, point.x, point.y);
// On décompose les champs de la structure pour les assigner
// à de nouvelles variables (i.e. my_x et my_y)
let
Point { x: my_x, y: my_y } = point;
let
_rectangle = Rectangle {
// L'instanciation de la structure est également une expression.
p1: Point { x: my_y, y: my_x },
p2: point,
};
// On instancie la structure unitaire, vide.
let
_nil = Nil;
// On instancie un tuple.
let
pair = Pair(1
, 0
.1
);
// Accède aux champs du tuple.
println!
("pair contains
{:?}
and
{:?}
"
, pair.0
, pair.1
);
// On décompose un tuple.
let
Pair(integer, decimal) = pair;
println!
("pair contains
{:?}
and
{:?}
"
, integer, decimal);
}
Activité
- Ajoutez une fonction rect_area qui calcule l'air d'un rectangle (essayez d'utiliser la déstructuration) ;
- Ajoutez une fonction square qui prend en paramètre une instance de la structure Point et un réel de type f32 puis renvoie une instance de la structure Rectangle contenant le point du coin inférieur gauche du rectangle ainsi qu'une largeur et une hauteur correspondant au réel passé en paramètre à la fonction square.
Voir aussi
3-2. Les énumérations▲
Le mot-clé enum
permet la création d'un type qui peut disposer d'une ou plusieurs variantes de lui-même. Toutes les variantes des structures sont valides dans une énumération.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
// Masque les avertissements du compilateur lorsqu'il y a
// du code mort présent dans votre code.
#![allow(dead_code)]
// On créé une énumération pour définir des "classes" de personnes.
// Notez que chaque variante de l'énumération est indépendante de l'autre.
// Aucune n'est égale à l'autre: `Engineer != Scientist` et
// `Height(i32) != Weight(i32)`.
enum
Person {
// Une variante peut être une structure unitaire,
Engineer,
Scientist,
// un tuple
Height(i32),
Weight(i32),
// ou simplement une structure classique.
Info { name: String, height: i32 }
}
// Prend une variante de l'énumération `Person` en argument et
// ne renvoie rien.
fn
inspect(p: Person) {
// En utilisant une énumération, vous devez analyser tous les cas
// possibles (obligatoire).
// Le pattern matching permet de les couvrir efficacement.
match
p {
Person::Engineer => println!
("Is an engineer!"
),
Person::Scientist => println!
("Is a scientist!"
),
// On récupère l'attribut de l'instance `Height`.
Person::Height(i) => println!
("Has a height of
{}
."
, i),
Person::Weight(i) => println!
("Has a weight of
{}
."
, i),
// Destructure `Info` into `name` and `height`.
// On récupère les attributs
Person::Info { name, height } => {
println!
("
{}
is
{}
tall!"
, name, height);
},
}
}
fn
main() {
let
person = Person::Height(18
);
let
amira = Person::Weight(10
);
// La fonction `to_owned()` créé une instance de la structure `String`
// possédée par l'assignation `name` à partir d'une slice (i.e. &str).
let
dave = Person::Info { name: "Dave"
.to_owned(), height: 72
};
let
rebecca = Person::Scientist;
let
rohan = Person::Engineer;
inspect(person);
inspect(amira);
inspect(dave);
inspect(rebecca);
inspect(rohan);
}
Voir aussi
Les attributs, le mot-clé match, le mot-clé fn, les chaînes de caractères.
3-2-1. Le mot-clé use▲
Grâce au mot-clé use
, il n'est pas toujours obligatoire de spécifier le contexte d'une ressource à chaque utilisation.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
// Masque les avertissements du compilateur concernant le code mort.
#![allow(dead_code)]
enum
Status {
Rich,
Poor,
}
enum
Work {
Civilian,
Soldier,
}
fn
main() {
// Nous précisons que ces variantes de l'énumération sont utilisées, donc
// il n'est plus nécessaire de préciser leur conteneur.
use
Status::{Poor, Rich};
// On utilise automatiquement toutes les variantes de l'enum `Work`.
use
Work::*;
// Equivalent à `Status::Poor`.
let
status = Poor;
// Equivalent à `Work::Civilian`.
let
work = Civilian;
match
status {
// Notez la disparition du conteneur lors de la recherche de pattern.
Rich => println!
("The rich have lots of money!"
),
Poor => println!
("The poor have no money..."
),
}
match
work {
// Une fois encore, le conteneur a disparu.
Civilian => println!
("Civilians work!"
),
Soldier => println!
("Soldiers fight!"
),
}
}
Voir aussi
3-2-2. Énumérations "C-like"▲
Les énumérations du langage Rust peuvent également adopter la même syntaxe que celles du langage C (possédant un identifiant explicite).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
// Un attribut qui masque les avertissements du compilateur
// concernant le code mort.
#![allow(dead_code)]
// Énumération avec un identifiant implicite (partant de 0).
enum
Number {
Zero, // 0
One, // 1
Two, // 2
}
// Énumération avec un identifiant explicite.
enum
Color {
Red = 0xff0000
,
Green = 0x00ff00
,
Blue = 0x0000ff
,
}
fn
main() {
// Les variantes d'une énumération peuvent être converties en entiers.
println!
("zero is
{}
"
, Number::Zero as
i32);
println!
("one is
{}
"
, Number::One as
i32);
println!
("roses are #
{:06x}
"
, Color::Red as
i32);
println!
("violets are #
{:06x}
"
, Color::Blue as
i32);
}
Voir aussi
3-2-3. Exemple d'utilisation : « linked-list »▲
Voici un exemple dans lequel une énumération peut être utilisée pour créer une liste de nœuds :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
use
List::*;
enum
List {
// Cons: Un tuple contenant un élément(i.e. u32) et un pointeur vers le noeud suivant (i.e. Box<List>).
Cons(u32, Box<List>),
// Nil: Un noeud témoignant de la fin de la liste.
Nil,
}
// Il est possible de lier, d'implémenter des méthodes
// pour une énumération.
impl
List {
// Créé une liste vide.
fn
new() -> List {
// `Nil` est une variante de `List`.
Nil
}
// Consomme, s'approprie la liste et renvoie une copie de cette même liste
// avec un nouvel élément ajouté à la suite.
fn
prepend(self
, elem: u32) -> List {
// `Cons` est également une variante de `List`.
Cons(elem, Box::new(self
))
}
// Renvoie la longueur de la liste.
fn
len(&
self
) -> u32 {
// `self` doit être analysé car le comportement de cette méthode
// dépend du type de variante auquel appartient `self`.
// `self` est de type `&List` et `*self` est de type `List`, rendant
// possible l'analyse directe de la ressource plutôt que par le biais d'un alias (i.e. une référence).
// Pour faire simple: on déréférence `self` avant de l'analyser.
// Note: Lorsque vous travaillez sur des références, préférez le déréférencement
// avant analyse.
match
*self
{
// On ne peut pas prendre "l'ownership" de la queue (liste)
// puisque l'on emprunte seulement `self` (nous ne le possédons pas);
// Nous créerons simplement une référence de la queue.
Cons(_, ref
tail) => 1
+ tail.len(),
// De base, une liste vide possède 0 élément.
Nil => 0
}
}
// Renvoie une représentation de la liste sous une chaîne de caractères
// (wrapper)
fn
stringify(&
self
) -> String {
match
*self
{
Cons(head, ref
tail) => {
// `format!` est équivalente à `println!` mais elle renvoie
// une chaîne de caractères allouée dans le tas (wrapper)
// plutôt que de l'afficher dans la console.
format!
("
{}
,
{}
"
, head, tail.stringify())
},
Nil => {
format!
("Nil"
)
},
}
}
}
fn
main() {
// Créé une liste vide.
let
mut
list = List::new();
// On ajoute quelques éléments.
list = list.prepend(1
);
list = list.prepend(2
);
list = list.prepend(3
);
// Affiche l'état définitif de la liste.
println!
("La linked list possède une longueur de:
{}
"
, list.len());
println!
("
{}
"
, list.stringify());
}
Voir aussi
3-3. Les constantes▲
Rust possède deux types de constantes qui peuvent être déclarées dans n'importe quel contexte global.
Chacun dispose d'un mot-clé :
-
const
: Une valeur immuable (état par défaut de toute variable) ; -
static
: Une variable pouvant être accédée en lecture et (accessoirement) en écriture possédant la « lifetime »'static
.
Exception pour les "chaînes de caractères"
littérales qui peuvent être directement assignées à une variable statique sans modification de votre part, car leur type &
'static
str dispose déjà de la lifetime 'static
. Tous les autres types de référence doivent être explicitement annotés pour étendre leur durée de vie.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
// Les variables globales sont déclarées en dehors de tous contextes.
static
LANGUAGE: &
'static
str = "Rust"
;
const
THRESHOLD: i32 = 10
;
fn
is_big(n: i32) -> bool {
// Accès à la constante dans une fonction.
n > THRESHOLD
}
fn
main() {
let
n = 16
;
// Accès à la constante dans le fil d'exécution principal.
println!
("This is
{}
"
, LANGUAGE);
println!
("The threshold is
{}
"
, THRESHOLD);
println!
("
{}
is
{}
"
, n, if
is_big(n) { "big"
} else
{ "small"
});
// Erreur! Vous ne pouvez pas modifier une constante.
// THRESHOLD = 5;
// FIXME ^ Commentez cette ligne pour voir disparaître
// le message d'erreur.
}
Voir aussi
La RFC des mot-clés const et static, la lifetime 'static.