IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Rust par l'exemple


précédentsommairesuivant

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 :

  1. struct : Définit une structure ;
  2. 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 :

  1. Les « tuple structs », aussi appelées simplement tuples ;
  2. Les structures classiques issues du langage C ;
  3. Les structures unitaires. Ne possèdant aucun champ, elles sont utiles pour la généricité.
 
Sélectionnez
1.
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é

  1. Ajoutez une fonction rect_area qui calcule l'air d'un rectangle (essayez d'utiliser la déstructuration) ;
  2. 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

Les attributs et la déstructuration.

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.

 
Sélectionnez
1.
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.

 
Sélectionnez
1.
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

Le mot-clé match et la déclaration use.

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).

 
Sélectionnez
1.
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

Le casting.

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 :

 
Sélectionnez
1.
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

Box, les méthodes.

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.

 
Sélectionnez
1.
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.


précédentsommairesuivant

Licence Creative Commons
Le contenu de cet article est rédigé par Rust Core Team et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.