- Sun 31 May 2015
- Rust
- #rust, #tutorials
Un des aspects absent de Rust à l'heure actuelle (1.0) mais toutefois assez demandé est l'héritage, comme on peut le trouver dans de nombreux autres langages de programmation.
Il y a somme toute deux approches à l'héritage : celle qui utilise les méthodes virtuelles, et celle qui ne les utilise pas.
Je m'intéresse ici à ce second cas. L'utilité principale est qu'on a un ensemble de struct
s qui
partagent toutes un certains nombres de champs, et qu'on veut réduire la duplication de code.
Par exemple, voyons une structure comme celle-ci, qui pourrait décrire des messages dans un salon de discussion:
struct PrivateMessage {
from: String,
content: String,
date: u64,
target_user: String
}
struct GroupMessage {
from: String,
content: String,
date: u64,
target_group: String
}
Ici, les trois premiers champs de chaque structure sont les mêmes, et un héritage depuis une
classe BaseMessage
, surtout si cette classe implémente plusieurs méthodes que l'on voudrait
utiliser sur tous nos messages.
L'approche serait donc de se tourner vers quelque chose de cet esprit:
struct BaseMessage {
from: String,
content: String,
date: u64
}
impl BaseMessage {
fn is_very_old(&self) -> bool { /* ... */ }
fn is_very_big(&self) -> bool { /* ... */ }
// and many other methods
}
struct PrivateMessage {
base: BaseMessage,
target_user: String
}
struct GroupMessage {
base: BaseMessage,
target_group: String
}
Mais là, bon, c'est bien gentil, mais c'est pas forcément très ergonomique de devoir se taper
my_message.base.is_very_big()
, ou bien my_message.base.date
pour récupérer la date. Avec un
héritage digne de ce nom, on aurait tout simplement my_message.is_very_big()
ou
my_message.date
.
Qu'à celà ne tienne, tout n'est pas perdu, il nous reste
Deref
! Ce trait permet de définir
l'opérateur *
de déréférencement sur nos structures. Rust procède à un auto-déréférencement pour
accéder aux attributs et aux méthodes des structures.
Par exemple, String
implémente Deref<Target=str>
, ce qui nous permet par exemple
d'appeller .contains(..)
sur une String
, alors que cette méthode n'est définir que sur &str
.
Vous avez deviné, il suffit de faire pareil sur nos messages:
use std::ops::Deref;
impl Deref for PrivateMessage {
type Target = BaseMessage;
fn deref(&self) -> &BaseMessage { &self.base }
}
impl Deref for GroupMessage {
type Target = BaseMessage;
fn deref(&self) -> &BaseMessage { &self.base }
}
Et voilà ! Une fois le trait implémenté, ça marche tout seul.
Si en plus vous voulez accéder à des méthodes mutables, il faut également implémenter le trait
DerefMut
. Ce dernier requiert que
vous ayez déjà implémenté Deref
et utilise le même type Target
.
impl DerefMut for PrivateMessage {
fn deref_mut(&mut self) -> &mut BaseMessage { &mut self.base }
}
impl DerefMut for GroupMessage {
fn deref_mut(&mut self) -> &mut BaseMessage { &mut self.base }
}
Et c'est tout ! Ça marche aussi simplement que l'héritage. On peut même récupérer un pointeur
&BaseMessage
via &*message
ou &message as &BaseMessage
. Pas de méthodes virtuelles par
contre, mais tant que ce n'est pas nécessaire, cette approche peut très bien convenir.