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 trait
Animal est alors implémenté pour le type Sheep, permettant l'utilisation des méthodes de Animal avec une instance du type Sheep.
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 {:?}.
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
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].
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
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.
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.
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 = [1
u32, 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().
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);
}