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