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

Rust par l'exemple


précédentsommairesuivant

14. Les traits

Un trait est un agrégat de méthodes définies pour un type inconnu : Self. Elles peuvent accéder aux autres méthodes déclarées dans le même trait.

Les traits peuvent être implémentés pour n'importe quel type de donnée. Dans l'exemple ci-dessous, nous définissons Animal, un groupe de méthodes. Le traitAnimal est alors implémenté pour le type Sheep, permettant l'utilisation des méthodes de Animal avec une instance du type Sheep.

 
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.
struct Sheep { naked: bool, name: &'static str }

trait Animal {
    // Méthode statique; `Self` fait référence au type ayant implémenté 
    // le trait.
    fn new(name: &'static str) -> Self;

    // Méthode d'instance; Elles renverront une chaîne de caractères.
    fn name(&self) -> &'static str;
    fn noise(&self) -> &'static str;

    // Les traits peuvent fournir une implémentation par défaut.
    fn talk(&self) {
        println!("{} says {}", self.name(), self.noise());
    }
}

impl Sheep {
    fn is_naked(&self) -> bool {
        self.naked
    }

    fn shear(&mut self) {
        if self.is_naked() {
            // Les méthodes de `Self` peuvent utiliser les méthodes déclarées 
            // par le trait.
            println!("{} is already naked...", self.name());
        } else {
            println!("{} gets a haircut!", self.name);

            self.naked = true;
        }
    }
}

// Implémentation des services du trait `Animal` 
// pour le type `Sheep`.
impl Animal for Sheep {
    // En l'occurrence, `Self` fait référence à `Sheep`.
    fn new(name: &'static str) -> Sheep {
        Sheep { name: name, naked: false }
    }

    fn name(&self) -> &'static str {
        self.name
    }

    fn noise(&self) -> &'static str {
        if self.is_naked() {
            "baaaaah?"
        } else {
            "baaaaah!"
        }
    }

    // L'implémentation par défaut fournie par le trait 
    // peut être réécrite.
    fn talk(&self) {
        // Par exemple, nous pourrions fournir une description plus précise.
        println!("{} pauses briefly... {}", self.name, self.noise());
    }
}

fn main() {
    // Typer l'identificateur est nécessaire dans ce cas de figure.
    let mut dolly: Sheep = Animal::new("Dolly");
    // TODO ^ Essayez de supprimer le type annoté.

    dolly.talk();
    dolly.shear();
    dolly.talk();
}

14-1. L'attribut Derive

Le compilateur est capable de fournir de simples implémentations pour cetains traits par le biais de l'attribut#[derive]. Ces traits peuvent toujours être implémentés manuellement si un traitement plus complexe est attendu.

Voici une liste de traits pouvant être dérivés :

  • Les traits de comparaison : Eq, PartialEq, Ord, PartialOrd;
  • Clone, pour créer une instance (T) à partir d'une référence (&T) par copie ;
  • Copy, pour permettre à un type d'être copié plutôt que transféré ;
  • Hash, pour générer un hash depuis &T ;
  • Debug, pour formater une valeur en utilisant le formatteur {:?}.
 
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.
// `Centimeters` est un tuple qui peut être comparé.
#[derive(PartialEq, PartialOrd)]
struct Centimeters(f64);

// `Inches` est un tuple qui peut être affiché.
#[derive(Debug)]
struct Inches(i32);

impl Inches {
    fn to_centimeters(&self) -> Centimeters {
        let &Inches(inches) = self;

        Centimeters(inches as f64 * 2.54)
    }
}

// `Seconds` est un tuple ne possédant aucun attribut.
struct Seconds(i32);

fn main() {
    let _one_second = Seconds(1);

    // Erreur: `Seconds` ne peut pas être affiché; Il n'implémente pas le trait `Debug`.
    // println!("One second looks like: {:?}", _one_second);
    // TODO ^ Essayez de décommenter cette ligne.

    // Erreur: `Seconds` ne peut pas être comparé; Il n'implémente pas le trait `PartialEq`.
    // let _this_is_true = (_one_second == _one_second);
    // TODO ^ Essayez de décommenter cette ligne.

    let foot = Inches(12);

    println!("One foot equals {:?}", foot);

    let meter = Centimeters(100.0);

    let cmp =
        if foot.to_centimeters() < meter {
            "smaller"
        } else {
            "bigger"
        };

    println!("One foot is {} than one meter.", cmp);
}

Voir aussi

Derive.

14-2. La surcharge des opérateurs

Avec Rust, nombre d'opérateurs peuvent être surchargés via les traits. En d'autres termes, des opérateurs peuvent être utilisés pour accomplir différentes tâches en fonction des arguments passés en entrée. Cette manipulation est possible parce que les opérateurs sont des sucres syntaxes visant à masquer l'appel des méthodes liées à ces derniers. Par exemple, l'opérateur + dans l'expression a + b appelle la méthode add (a.add(b)). La méthode add appartient au trait Add, d'où l'utilisation de l'opérateur + par tous les types implémentant le trait.

Vous pouvez retrouver la liste des traits surchargeant des opérateurs [ici][operators].

 
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.
use std::ops;

struct Foo;
struct Bar;

#[derive(Debug)]
struct FooBar;

#[derive(Debug)]
struct BarFoo;

// Le trait `std::ops::Add` est utilisé pour permettre la surcharge de `+`.
// Ici, nous spécifions `Add<Bar>` - cette implémentation sera appelée si l'opérande de droite est 
// de type `Bar`.
// Le bloc ci-dessous implémente l'opération : `Foo + Bar = FooBar`.
impl ops::Add<Bar> for Foo {
    type Output = FooBar;

    fn add(self, _rhs: Bar) -> FooBar {
        println!("> Foo.add(Bar) was called");

        FooBar
    }
}

// En inversant les types, nous nous retrouvons à implémenter une addition non-commutative.
// Ici, nous spécifions `Add<Foo>` - cette implémentation sera appelée si l'opérande de droite 
// est de type `Foo`.
// Le bloc ci-dessous implémente l'opération: `Bar + Foo = BarFoo`.
impl ops::Add<Foo> for Bar {
    type Output = BarFoo;

    fn add(self, _rhs: Foo) -> BarFoo {
        println!("> Bar.add(Foo) was called");

        BarFoo
    }
}

fn main() {
    println!("Foo + Bar = {:?}", Foo + Bar);
    println!("Bar + Foo = {:?}", Bar + Foo);
}

Voir aussi

Add, index de la syntaxe.

14-3. Le trait Drop

Le trait Drop ne possède qu'une seule méthode : drop ; cette dernière est automatiquement appelée lorsqu'un objet sort du contexte. La fonction principale du trait Drop est de libérer les ressources que les instances, du type ayant implémenté le trait, possèdent.

Box, Vec, String, File et Process sont autant d'exemples de types qui implémentent le trait Drop pour libérer leurs ressources. Le trait Drop peut également être implémenté manuellement pour répondre aux besoins de vos propres types.

Dans l'exemple suivant, nous affichons quelque chose dans la console à partir de la méthode drop pour notifier chaque appel.

 
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.
struct Droppable {
    name: &'static str,
}

// Implémentation basique de `drop` qui affiche un message dans la console.
impl Drop for Droppable {
    fn drop(&mut self) {
        println!("> Dropping {}", self.name);
    }
}

fn main() {
    let _a = Droppable { name: "a" };

    // block A
    {
        let _b = Droppable { name: "b" };

        // block B
        {
            let _c = Droppable { name: "c" };
            let _d = Droppable { name: "d" };

            println!("Exiting block B");
        }
        println!("Just exited block B");

        println!("Exiting block A");
    }
    println!("Just exited block A");

    // La variable peut être libérée manuellement en utilisant la fonction `(std::mem::)drop`.
    drop(_a);
    // TODO ^ Essayez de commenter cette ligne.

    println!("end of the main function");

    // `_a` ne *sera pas* libérée une seconde fois ici puisque nous l'avons déjà fait 
    // plus haut, manuellement.
}

14-4. Les itérateurs

Le trait Iterator est utilisé pour implémenter les itérateurs sur les collections tels que les tableaux.

Le trait nécessite seulement la définition d'une méthode pour l'élément next. Elle peut être implémentée manuellement en utilisant un bloc impl ou automatiquement (comme pour les tableaux et intervalles).

Pour les utilisations les plus communes, la boucle for peut convertir certaines collections en itérateurs en utilisant la méthode .into_iterator().

Les méthodes pouvant être appelées en utilisant le trait Iterator, en plus de celles présentées dans l'exemple ci-dessous, sont listées ici.

 
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.
struct Fibonacci {
    curr: u32,
    next: u32,
}

// Implémentation de `Iterator` pour le type `Fibonacci`.
// Le trait `Iterator` nécessite l'implémentation d'une méthode seulement pour l'élément `next`.
impl Iterator for Fibonacci {
    type Item = u32;

    // Ici, nous définissons la séquence utilisant `.curr` et `.next`.
    // Le type de renvoi est `Option<T>`:
    //     * Lorsque l'`Iterator` est terminé, `None` est renvoyé;
    //     * Autrement, la valeur suivante est enveloppé dans une instance `Some` et renvoyée.
    fn next(&mut self) -> Option<u32> {
        let new_next = self.curr + self.next;

        self.curr = self.next;
        self.next = new_next;

        // Puisqu'il n'y a pas de limite à une suite de Fibonacci, l'`Iterator` 
        // ne renverra jamais `None`.
        Some(self.curr)
    }
}

// Renvoie un générateur de suites de Fibonacci.
fn fibonacci() -> Fibonacci {
    Fibonacci { curr: 1, next: 1 }
}

fn main() {
    // `0..3` est un `Iterator` qui génère: 0, 1, et 2 (i.e. intervalle `[0, 3[`).
    let mut sequence = 0..3;

    println!("Four consecutive `next` calls on 0..3");
    println!("> {:?}", sequence.next());
    println!("> {:?}", sequence.next());
    println!("> {:?}", sequence.next());
    println!("> {:?}", sequence.next());


    // `for` parcourt un `Iterator` jusqu'à ce qu'il renvoie `None`.
    // Chaque valeur contenue par un objet `Some` est libérée de son conteneur 
    // puis assignée à une variable (en l'occurrence, `i`).
    println!("Iterate through 0..3 using `for`");
    for i in 0..3 {
        println!("> {}", i);
    }

    // La méthode `take(n)` réduit un `Iterator` à ces `n` premiers éléments.
    println!("The first four terms of the Fibonacci sequence are: ");
    for i in fibonacci().take(4) {
        println!("> {}", i);
    }

    // La méthode `skip(n)` tronque les `n` premiers éléments d'un `Iterator`.
    println!("The next four terms of the Fibonacci sequence are: ");
    for i in fibonacci().skip(4).take(4) {
        println!("> {}", i);
    }

    let array = [1u32, 3, 3, 7];

    // La méthode `iter` construit un `Iterator` sur un(e) tableau/slice.
    println!("Iterate the following array {:?}", &array);
    for i in array.iter() {
        println!("> {}", i);
    }
}

14-5. Le trait Clone

Lors du traitement des ressources, le comportement par défaut est de les transférer lors d'un assignement ou un appel de méthode. Cependant, il est parfois nécessaire d'effectuer une copie des ressources en question.

C'est exactement la fonction du trait Clone, qui nous permettra d'utiliser la méthode clone().

 
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.
// Une structure unitaire sans ressources.
#[derive(Debug, Clone, Copy)]
struct Nil;

// Un tuple avec des ressources qui implémente le trait `Clone`.
#[derive(Clone, Debug)]
struct Pair(Box<i32>, Box<i32>);

fn main() {
    // Un instancie `Nil`.
    let nil = Nil;
    // On copie l'objet `Nil`, aucune ressource à transférer.
    let copied_nil = nil;

    // Les deux instances peuvent être utilisées indépendament l'une de l'autre.
    println!("original: {:?}", nil);
    println!("copy: {:?}", copied_nil);

    // On crée un objet `Pair`.
    let pair = Pair(Box::new(1), Box::new(2));
    println!("original: {:?}", pair);

    // On copie `pair` dans `moved_pair`, transfert de ressources.
    let moved_pair = pair;
    println!("copy: {:?}", moved_pair);

    // Erreur! `pair` ne possède plus ses ressources.
    // println!("original: {:?}", pair);
    // TODO ^ Essayez de décommenter cette ligne.

    // On copie `moved_pair` dans `cloned_pair` (ressources incluses).
    let cloned_pair = moved_pair.clone();
    // On libère la ressource originale avec `std::mem::drop`.
    drop(moved_pair);

    // Erreur! `moved_pair` a été libérée.
    // println!("copy: {:?}", moved_pair);
    // TODO ^ Essayez de décommenter cette ligne.

    // La copie obtenue par `.clone()` peut toujours être utilisée !
    println!("clone: {:?}", cloned_pair);
}

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.